mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Add comprehensive unit tests for WindowsKeyConverter - Implement 118 parallelizable unit tests for WindowsKeyConverter - Cover ToKey and ToKeyInfo methods with full bidirectional testing - Test basic characters, modifiers, special keys, function keys - Test VK_PACKET Unicode/IME input - Test OEM keys, NumPad keys, and lock states - Include round-trip conversion tests - All tests passing successfully Fixes #4389 * Rename `start` parameter to `viewportXOffset` for clarity The `start` parameter in several methods and interfaces has been renamed to `viewportXOffset` to better reflect its purpose as the horizontal offset of the viewport during string rendering. - Updated method signatures in `ListViewWithSelection` to use `viewportXOffset` instead of `start`, including default values. - Modified the `RenderUstr` method in `ListViewWithSelection` to use `viewportXOffset` for calculating the starting index. - Renamed the `start` parameter to `viewportXOffset` in the `IListDataSource` interface and updated its documentation. - Replaced all occurrences of `start` with `viewportXOffset` in the `ListWrapper<T>` class, including method calls and logic. - Updated the `RenderUstr` method in `ListWrapper<T>` to use `viewportXOffset` for substring calculations. - Adjusted the test method in `ListViewTests.cs` to reflect the parameter name change. These changes improve code readability and make the parameter's role in rendering logic more explicit. * Remove WindowsKeyConverterTests class that was added by mistake * Modernized ListView and IListDataSource - Tons of new unit tests Refactored `ListView` and `IListDataSource` to improve readability, maintainability, and functionality. Introduced `ListWrapper<T>` as a default implementation of `IListDataSource` for easier integration with standard collections. Enhanced `ListView` with better handling of marking, selection, and scrolling. Replaced `viewportXOffset` with `viewportX` for horizontal scrolling. Added `EnsureSelectedItemVisible` to maintain visibility of the selected item. Updated `IListDataSource` with detailed XML documentation and added `SuspendCollectionChangedEvent` for bulk updates. Improved null safety with nullable reference types. Added comprehensive unit tests for `ListWrapper<T>` and `IListDataSource` to ensure robustness. Modernized the codebase with C# features like expression-bodied members and pattern matching. Fixed bugs related to `SelectedItem` validation and rendering artifacts. * Improve index validation in ComboBox and ListView Enhance robustness by adding stricter checks for valid indices in ComboBox and ListView. Updated conditions in the `_listview.SelectedItemChanged` event handler to ensure `e.Item` is non-negative before accessing `_searchSet`. Modified the `SetValue` method to use `e.Item` instead of `_listview.SelectedItem`. In ListView, updated the `OnSelectedChanged` method to validate that `SelectedItem` is non-negative (`>= 0`) before accessing the `Source` list. These changes prevent potential out-of-range errors and improve code safety. * Refactor and enhance test coverage across modules Refactored and added new tests to improve coverage, readability, and consistency across multiple test files. Key changes include: - **ShortcutTests.cs**: Added tests for `BindKeyToApplication` and removed redundant tests. - **SourcesManagerTests.cs**: Renamed `Update_*` tests to `Load_*` for clarity. - **ArrangementTests.cs**: Reintroduced `MouseGrabHandler` tests, added `ViewArrangement` flag tests, and improved structure. - **NeedsDrawTests.cs**: Replaced `Application.Screen.Size` with fixed dimensions for better isolation. - **DimAutoTests.cs**: Updated layout tests to use fixed dimensions. - **FrameTests.cs**: Standardized object initialization and validated frame behavior. - **SubViewTests.cs**: Improved formatting and modernized event handling. - **NumericUpDownTests.cs**: Decoupled layout tests from screen size. General improvements: - Enhanced formatting and removed redundant tests. - Added comments for clarity. - Introduced `ITestOutputHelper` for better debugging in `ArrangementTests`. * Refactor to use nullable types for better null safety Enabled nullable reference types across the codebase to improve null safety and prevent potential null reference issues. Refactored `SelectedItem` and related properties from `int` to `int?` to represent no selection with `null` instead of `-1`. Updated logic, event arguments, and method signatures to handle nullable values consistently. Simplified object initialization using modern C# syntax and improved code readability with interpolated strings. Added null checks and early returns to prevent runtime errors. Enhanced error handling by throwing `ArgumentOutOfRangeException` for invalid values. Updated tests to reflect the changes, replacing assertions for `-1` with `null` and ensuring proper handling of nullable values. Cleaned up redundant code and improved formatting for better maintainability. * on` functionality has been deprecated, refactored, or removed from the `Shortcut` class. * Refactor: Transition to instance-based architecture Updated `Run-LocalCoverage.ps1` to increase `--blame-hang-timeout` from 10s to 60s. Improved null safety in `GuiTestContext` by adding null-conditional operators. Commented out problematic code in `SetupFakeApplicationAttribute.cs` to prevent test hangs. Excluded `ViewBase` files from `UnitTests.Parallelizable.csproj` and removed redundant folder declarations. Simplified event handling in `IListDataSourceTests.cs` and updated `ListViewTests.cs` to use nullable reference types. Enhanced documentation to emphasize the transition to an instance-based application architecture. Updated examples in `application.md`, `multitasking.md`, and `navigation.md` to reflect the use of `Application.Create()` and `View.App`. Clarified the obsolescence of the static `Application` class. Revised table of contents in `toc.yml` to include new sections like "Application Deep Dive" and "Scheme Deep Dive." Added `dotnet-tools.json` for tool configuration. These changes improve maintainability, testability, and alignment with modern C# practices. * Refactor ListViewTests to use Terminal.Gui framework The `ListViewTests` class has been refactored to replace the `AutoInitShutdown` attribute with explicit application lifecycle management using `IApplication` and `app.Init()` from the `Terminal.Gui` framework. Key changes include: - Rewriting tests to use `Terminal.Gui`'s application lifecycle. - Adding a private `_output` field for logging test output via `ITestOutputHelper`. - Updating `DriverAssert.AssertDriverContentsWithFrameAre` to include `app.Driver` for UI verification. - Rewriting tests like `Clicking_On_Border_Is_Ignored`, `EnsureSelectedItemVisible_SelectedItem`, and others to align with the new framework. - Adding explicit calls to `app.Shutdown()` for proper cleanup. - Enabling nullable reference types with `#nullable enable`. - Updating `using` directives and `namespace` to reflect the new structure. These changes improve test maintainability, compatibility, and diagnostics. * Update Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/Views/CollectionNavigation/CollectionNavigatorBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Examples/UICatalog/UICatalogTop.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/Views/ListWrapper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Terminal.Gui/Views/ListWrapper.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated the `SetMark` method to return `Source.IsMarked(SelectedItem.Value)` for consistency and removed an outdated comment questioning its correctness. Enhanced the exception message in the `SelectedItem` property setter to provide clearer guidance when the value is out of range. * Add comprehensive ListView behavior test coverage Added multiple test methods to validate `ListView` behavior: - `Vertical_ScrollBar_Hides_And_Shows_As_Needed`: Ensures the vertical scrollbar auto-hides/shows based on content height. - `Mouse_Wheel_Scrolls`: Verifies vertical scrolling with the mouse wheel updates `TopItem`. - `SelectedItem_With_Source_Null_Does_Nothing`: Confirms no exceptions occur when setting `SelectedItem` with a `null` source. - `Horizontal_Scroll`: Tests horizontal scrolling, including programmatic and mouse wheel interactions, ensuring `LeftItem` updates correctly. - `SetSourceAsync_SetsSource`: Validates the asynchronous `SetSourceAsync` method updates the source and item count. - `AllowsMultipleSelection_Set_To_False_Unmarks_All_But_Selected`: Ensures disabling multiple selection unmarks all but the selected item. - `Source_CollectionChanged_Remove`: Confirms `SelectedItem` and source count update correctly when items are removed from the source collection. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
768 lines
30 KiB
C#
768 lines
30 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.Extensions.Logging;
|
|
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
|
|
|
|
#nullable enable
|
|
|
|
namespace UICatalog;
|
|
|
|
/// <summary>
|
|
/// This is the main UI Catalog app view. It is run fresh when the app loads (if a Scenario has not been passed on
|
|
/// the command line) and each time a Scenario ends.
|
|
/// </summary>
|
|
public class UICatalogTop : Toplevel
|
|
{
|
|
// When a scenario is run, the main app is killed. The static
|
|
// members are cached so that when the scenario exits the
|
|
// main app UI can be restored to previous state
|
|
|
|
// Note, we used to pass this to scenarios that run, but it just added complexity
|
|
// So that was removed. But we still have this here to demonstrate how changing
|
|
// the scheme works.
|
|
public static string? CachedTopLevelScheme { get; set; }
|
|
|
|
// Diagnostics
|
|
private static ViewDiagnosticFlags _diagnosticFlags;
|
|
|
|
public UICatalogTop ()
|
|
{
|
|
_diagnosticFlags = Diagnostics;
|
|
|
|
_menuBar = CreateMenuBar ();
|
|
_statusBar = CreateStatusBar ();
|
|
_categoryList = CreateCategoryList ();
|
|
_scenarioList = CreateScenarioList ();
|
|
|
|
Add (_menuBar, _categoryList, _scenarioList, _statusBar);
|
|
|
|
Loaded += LoadedHandler;
|
|
Unloaded += UnloadedHandler;
|
|
|
|
// Restore previous selections
|
|
if (_categoryList.Source?.Count > 0) {
|
|
_categoryList.SelectedItem = _cachedCategoryIndex ?? 0;
|
|
} else {
|
|
_categoryList.SelectedItem = null;
|
|
}
|
|
_scenarioList.SelectedRow = _cachedScenarioIndex;
|
|
|
|
SchemeName = CachedTopLevelScheme = SchemeManager.SchemesToSchemeName (Schemes.Base);
|
|
ConfigurationManager.Applied += ConfigAppliedHandler;
|
|
}
|
|
|
|
|
|
private static bool _isFirstRunning = true;
|
|
|
|
private void LoadedHandler (object? sender, EventArgs? args)
|
|
{
|
|
if (_disableMouseCb is { })
|
|
{
|
|
_disableMouseCb.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked;
|
|
}
|
|
|
|
if (_shVersion is { })
|
|
{
|
|
_shVersion.Title = $"{RuntimeEnvironment.OperatingSystem} {RuntimeEnvironment.OperatingSystemVersion}, {Application.Driver!.GetVersionInfo ()}";
|
|
}
|
|
|
|
if (CachedSelectedScenario != null)
|
|
{
|
|
CachedSelectedScenario = null;
|
|
_isFirstRunning = false;
|
|
}
|
|
|
|
if (!_isFirstRunning)
|
|
{
|
|
_scenarioList.SetFocus ();
|
|
}
|
|
|
|
if (_statusBar is { })
|
|
{
|
|
_statusBar.VisibleChanged += (s, e) => { ShowStatusBar = _statusBar.Visible; };
|
|
}
|
|
|
|
Loaded -= LoadedHandler;
|
|
_categoryList!.EnsureSelectedItemVisible ();
|
|
_scenarioList.EnsureSelectedCellIsVisible ();
|
|
}
|
|
|
|
private void UnloadedHandler (object? sender, EventArgs? args)
|
|
{
|
|
ConfigurationManager.Applied -= ConfigAppliedHandler;
|
|
Unloaded -= UnloadedHandler;
|
|
}
|
|
|
|
#region MenuBar
|
|
|
|
private readonly MenuBarv2? _menuBar;
|
|
private CheckBox? _force16ColorsMenuItemCb;
|
|
private OptionSelector? _themesSelector;
|
|
private OptionSelector? _topSchemesSelector;
|
|
private OptionSelector? _logLevelSelector;
|
|
private FlagSelector<ViewDiagnosticFlags>? _diagnosticFlagsSelector;
|
|
private CheckBox? _disableMouseCb;
|
|
|
|
private MenuBarv2 CreateMenuBar ()
|
|
{
|
|
MenuBarv2 menuBar = new (
|
|
[
|
|
new (
|
|
"_File",
|
|
[
|
|
new MenuItemv2 ()
|
|
{
|
|
Title ="_Quit",
|
|
HelpText = "Quit UI Catalog",
|
|
Key = Application.QuitKey,
|
|
// By not specifying TargetView the Key Binding will be Application-level
|
|
Command = Command.Quit
|
|
}
|
|
]),
|
|
new ("_Themes", CreateThemeMenuItems ()),
|
|
new ("Diag_nostics", CreateDiagnosticMenuItems ()),
|
|
new ("_Logging", CreateLoggingMenuItems ()),
|
|
new (
|
|
"_Help",
|
|
[
|
|
new MenuItemv2 (
|
|
"_Documentation",
|
|
"API docs",
|
|
() => OpenUrl ("https://gui-cs.github.io/Terminal.Gui"),
|
|
Key.F1
|
|
),
|
|
new MenuItemv2 (
|
|
"_README",
|
|
"Project readme",
|
|
() => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"),
|
|
Key.F2
|
|
),
|
|
new MenuItemv2 (
|
|
"_About...",
|
|
"About UI Catalog",
|
|
() => MessageBox.Query (
|
|
"",
|
|
GetAboutBoxMessage (),
|
|
wrapMessage: false,
|
|
buttons: "_Ok"
|
|
),
|
|
Key.A.WithCtrl
|
|
)
|
|
])
|
|
])
|
|
{
|
|
Title = "menuBar",
|
|
Id = "menuBar"
|
|
};
|
|
|
|
return menuBar;
|
|
|
|
View [] CreateThemeMenuItems ()
|
|
{
|
|
List<View> menuItems = [];
|
|
|
|
_force16ColorsMenuItemCb = new ()
|
|
{
|
|
Title = "Force _16 Colors",
|
|
CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
|
|
// Best practice for CheckBoxes in menus is to disable focus and highlight states
|
|
CanFocus = false,
|
|
HighlightStates = MouseState.None
|
|
};
|
|
|
|
_force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
|
|
{
|
|
if (Application.Force16Colors
|
|
&& args.Result == CheckState.UnChecked
|
|
&& !Application.Driver!.SupportsTrueColor)
|
|
{
|
|
args.Handled = true;
|
|
}
|
|
};
|
|
|
|
_force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) =>
|
|
{
|
|
Application.Force16Colors = args.Value == CheckState.Checked;
|
|
|
|
_force16ColorsShortcutCb!.CheckedState = args.Value;
|
|
Application.LayoutAndDraw ();
|
|
};
|
|
|
|
menuItems.Add (
|
|
new MenuItemv2
|
|
{
|
|
CommandView = _force16ColorsMenuItemCb
|
|
});
|
|
|
|
menuItems.Add (new Line ());
|
|
|
|
if (ConfigurationManager.IsEnabled)
|
|
{
|
|
_themesSelector = new ()
|
|
{
|
|
// HighlightStates = MouseState.In,
|
|
CanFocus = true,
|
|
// InvertFocusAttribute = true
|
|
};
|
|
|
|
_themesSelector.ValueChanged += (_, args) =>
|
|
{
|
|
if (args.Value is null)
|
|
{
|
|
return;
|
|
}
|
|
ThemeManager.Theme = ThemeManager.GetThemeNames () [(int)args.Value];
|
|
|
|
};
|
|
|
|
var menuItem = new MenuItemv2
|
|
{
|
|
CommandView = _themesSelector,
|
|
HelpText = "Cycle Through Themes",
|
|
Key = Key.T.WithCtrl
|
|
};
|
|
menuItems.Add (menuItem);
|
|
|
|
menuItems.Add (new Line ());
|
|
|
|
_topSchemesSelector = new ()
|
|
{
|
|
// HighlightStates = MouseState.In,
|
|
};
|
|
|
|
_topSchemesSelector.ValueChanged += (_, args) =>
|
|
{
|
|
if (args.Value is null)
|
|
{
|
|
return;
|
|
}
|
|
CachedTopLevelScheme = SchemeManager.GetSchemesForCurrentTheme ()!.Keys.ToArray () [(int)args.Value];
|
|
SchemeName = CachedTopLevelScheme;
|
|
SetNeedsDraw ();
|
|
};
|
|
|
|
menuItem = new ()
|
|
{
|
|
Title = "Scheme for Toplevel",
|
|
SubMenu = new (
|
|
[
|
|
new ()
|
|
{
|
|
CommandView = _topSchemesSelector,
|
|
HelpText = "Cycle Through schemes",
|
|
Key = Key.S.WithCtrl
|
|
}
|
|
])
|
|
};
|
|
menuItems.Add (menuItem);
|
|
|
|
UpdateThemesMenu ();
|
|
}
|
|
else
|
|
{
|
|
menuItems.Add (new MenuItemv2 ()
|
|
{
|
|
Title = "Configuration Manager is not Enabled",
|
|
Enabled = false
|
|
});
|
|
}
|
|
|
|
return menuItems.ToArray ();
|
|
}
|
|
|
|
View [] CreateDiagnosticMenuItems ()
|
|
{
|
|
List<View> menuItems = [];
|
|
|
|
_diagnosticFlagsSelector = new ()
|
|
{
|
|
Styles = SelectorStyles.ShowNoneFlag,
|
|
CanFocus = true
|
|
|
|
};
|
|
_diagnosticFlagsSelector.UsedHotKeys.Add (Key.D);
|
|
_diagnosticFlagsSelector.AssignHotKeys = true;
|
|
_diagnosticFlagsSelector.Value = Diagnostics;
|
|
_diagnosticFlagsSelector.ValueChanged += (sender, args) =>
|
|
{
|
|
_diagnosticFlags = (ViewDiagnosticFlags)_diagnosticFlagsSelector.Value;
|
|
Diagnostics = _diagnosticFlags;
|
|
};
|
|
|
|
menuItems.Add (
|
|
new MenuItemv2
|
|
{
|
|
CommandView = _diagnosticFlagsSelector,
|
|
HelpText = "View Diagnostics"
|
|
});
|
|
|
|
menuItems.Add (new Line ());
|
|
|
|
_disableMouseCb = new ()
|
|
{
|
|
Title = "_Disable Mouse",
|
|
CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked,
|
|
// Best practice for CheckBoxes in menus is to disable focus and highlight states
|
|
CanFocus = false,
|
|
HighlightStates = MouseState.None
|
|
};
|
|
|
|
_disableMouseCb.CheckedStateChanged += (_, args) => { Application.IsMouseDisabled = args.Value == CheckState.Checked; };
|
|
|
|
menuItems.Add (
|
|
new MenuItemv2
|
|
{
|
|
CommandView = _disableMouseCb,
|
|
HelpText = "Disable Mouse"
|
|
});
|
|
|
|
return menuItems.ToArray ();
|
|
}
|
|
|
|
View [] CreateLoggingMenuItems ()
|
|
{
|
|
List<View?> menuItems = [];
|
|
|
|
LogLevel [] logLevels = Enum.GetValues<LogLevel> ();
|
|
|
|
_logLevelSelector = new ()
|
|
{
|
|
AssignHotKeys = true,
|
|
Labels = Enum.GetNames<LogLevel> (),
|
|
Value = logLevels.ToList ().IndexOf (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel)),
|
|
// HighlightStates = MouseState.In,
|
|
};
|
|
|
|
_logLevelSelector.ValueChanged += (_, args) =>
|
|
{
|
|
UICatalog.Options = UICatalog.Options with { DebugLogLevel = Enum.GetName (logLevels [args.Value!.Value])! };
|
|
|
|
UICatalog.LogLevelSwitch.MinimumLevel =
|
|
UICatalog.LogLevelToLogEventLevel (Enum.Parse<LogLevel> (UICatalog.Options.DebugLogLevel));
|
|
};
|
|
|
|
menuItems.Add (
|
|
new MenuItemv2
|
|
{
|
|
CommandView = _logLevelSelector,
|
|
HelpText = "Cycle Through Log Levels",
|
|
Key = Key.L.WithCtrl
|
|
});
|
|
|
|
// add a separator
|
|
menuItems.Add (new Line ());
|
|
|
|
menuItems.Add (
|
|
new MenuItemv2 (
|
|
"_Open Log Folder",
|
|
string.Empty,
|
|
() => OpenUrl (UICatalog.LOGFILE_LOCATION)
|
|
));
|
|
|
|
return menuItems.ToArray ()!;
|
|
}
|
|
|
|
}
|
|
|
|
private void UpdateThemesMenu ()
|
|
{
|
|
if (_themesSelector is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_themesSelector.Value = null;
|
|
_themesSelector.AssignHotKeys = true;
|
|
_themesSelector.UsedHotKeys.Clear ();
|
|
_themesSelector.Labels = ThemeManager.GetThemeNames ().ToArray ();
|
|
_themesSelector.Value = ThemeManager.GetThemeNames ().IndexOf (ThemeManager.GetCurrentThemeName ());
|
|
|
|
if (_topSchemesSelector is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_topSchemesSelector.AssignHotKeys = true;
|
|
_topSchemesSelector.UsedHotKeys.Clear ();
|
|
int? selectedScheme = _topSchemesSelector.Value;
|
|
_topSchemesSelector.Labels = SchemeManager.GetSchemeNames ().ToArray ();
|
|
_topSchemesSelector.Value = selectedScheme;
|
|
|
|
if (CachedTopLevelScheme is null || !SchemeManager.GetSchemeNames ().Contains (CachedTopLevelScheme))
|
|
{
|
|
CachedTopLevelScheme = SchemeManager.SchemesToSchemeName (Schemes.Base);
|
|
}
|
|
|
|
int newSelectedItem = SchemeManager.GetSchemeNames ().IndexOf (CachedTopLevelScheme!);
|
|
// if the item is in bounds then select it
|
|
if (newSelectedItem >= 0 && newSelectedItem < SchemeManager.GetSchemeNames ().Count)
|
|
{
|
|
_topSchemesSelector.Value = newSelectedItem;
|
|
}
|
|
}
|
|
|
|
#endregion MenuBar
|
|
|
|
#region Scenario List
|
|
|
|
private readonly TableView _scenarioList;
|
|
|
|
private static int _cachedScenarioIndex;
|
|
|
|
public static ObservableCollection<Scenario>? CachedScenarios { get; set; }
|
|
|
|
// If set, holds the scenario the user selected to run
|
|
public static Scenario? CachedSelectedScenario { get; set; }
|
|
|
|
private TableView CreateScenarioList ()
|
|
{
|
|
// Create the scenario list. The contents of the scenario list changes whenever the
|
|
// Category list selection changes (to show just the scenarios that belong to the selected
|
|
// category).
|
|
TableView scenarioList = new ()
|
|
{
|
|
X = Pos.Right (_categoryList!) - 1,
|
|
Y = Pos.Bottom (_menuBar!),
|
|
Width = Dim.Fill (),
|
|
Height = Dim.Fill (Dim.Func (v => v!.Frame.Height, _statusBar)),
|
|
//AllowsMarking = false,
|
|
CanFocus = true,
|
|
Title = "_Scenarios",
|
|
BorderStyle = _categoryList!.BorderStyle,
|
|
SuperViewRendersLineCanvas = true
|
|
};
|
|
|
|
// TableView provides many options for table headers. For simplicity, we turn all
|
|
// of these off. By enabling FullRowSelect and turning off headers, TableView looks just
|
|
// like a ListView
|
|
scenarioList.FullRowSelect = true;
|
|
scenarioList.Style.ShowHeaders = false;
|
|
scenarioList.Style.ShowHorizontalHeaderOverline = false;
|
|
scenarioList.Style.ShowHorizontalHeaderUnderline = false;
|
|
scenarioList.Style.ShowHorizontalBottomline = false;
|
|
scenarioList.Style.ShowVerticalCellLines = false;
|
|
scenarioList.Style.ShowVerticalHeaderLines = false;
|
|
|
|
/* By default, TableView lays out columns at render time and only
|
|
* measures y rows of data at a time. Where y is the height of the
|
|
* console. This is for the following reasons:
|
|
*
|
|
* - Performance, when tables have a large amount of data
|
|
* - Defensive, prevents a single wide cell value pushing other
|
|
* columns off-screen (requiring horizontal scrolling
|
|
*
|
|
* In the case of UICatalog here, such an approach is overkill so
|
|
* we just measure all the data ourselves and set the appropriate
|
|
* max widths as ColumnStyles
|
|
*/
|
|
int longestName = CachedScenarios!.Max (s => s.GetName ().Length);
|
|
|
|
scenarioList.Style.ColumnStyles.Add (
|
|
0,
|
|
new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName }
|
|
);
|
|
scenarioList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1 });
|
|
scenarioList.CellActivated += ScenarioView_OpenSelectedItem;
|
|
|
|
// TableView typically is a grid where nav keys are biased for moving left/right.
|
|
scenarioList.KeyBindings.Remove (Key.Home);
|
|
scenarioList.KeyBindings.Add (Key.Home, Command.Start);
|
|
scenarioList.KeyBindings.Remove (Key.End);
|
|
scenarioList.KeyBindings.Add (Key.End, Command.End);
|
|
|
|
// Ideally, TableView.MultiSelect = false would turn off any keybindings for
|
|
// multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
|
|
// a shortcut to About.
|
|
scenarioList.MultiSelect = false;
|
|
scenarioList.KeyBindings.Remove (Key.A.WithCtrl);
|
|
|
|
return scenarioList;
|
|
}
|
|
|
|
|
|
/// <summary>Launches the selected scenario, setting the global _selectedScenario</summary>
|
|
/// <param name="e"></param>
|
|
private void ScenarioView_OpenSelectedItem (object? sender, EventArgs? e)
|
|
{
|
|
if (CachedSelectedScenario is null)
|
|
{
|
|
// Save selected item state
|
|
_cachedCategoryIndex = _categoryList!.SelectedItem;
|
|
_cachedScenarioIndex = _scenarioList.SelectedRow;
|
|
|
|
// Create new instance of scenario (even though Scenarios contains instances)
|
|
var selectedScenarioName = (string)_scenarioList.Table [_scenarioList.SelectedRow, 0];
|
|
|
|
CachedSelectedScenario = (Scenario)Activator.CreateInstance (
|
|
CachedScenarios!.FirstOrDefault (
|
|
s => s.GetName ()
|
|
== selectedScenarioName
|
|
)!
|
|
.GetType ()
|
|
)!;
|
|
|
|
// Tell the main app to stop
|
|
Application.RequestStop ();
|
|
}
|
|
}
|
|
|
|
#endregion Scenario List
|
|
|
|
#region Category List
|
|
|
|
private readonly ListView? _categoryList;
|
|
private static int? _cachedCategoryIndex;
|
|
public static ObservableCollection<string>? CachedCategories { get; set; }
|
|
|
|
private ListView CreateCategoryList ()
|
|
{
|
|
// Create the Category list view. This list never changes.
|
|
ListView categoryList = new ()
|
|
{
|
|
X = 0,
|
|
Y = Pos.Bottom (_menuBar!),
|
|
Width = Dim.Auto (),
|
|
Height = Dim.Fill (Dim.Func (v => v!.Frame.Height, _statusBar)),
|
|
AllowsMarking = false,
|
|
CanFocus = true,
|
|
Title = "_Categories",
|
|
BorderStyle = LineStyle.Rounded,
|
|
SuperViewRendersLineCanvas = true,
|
|
Source = new ListWrapper<string> (CachedCategories)
|
|
};
|
|
categoryList.OpenSelectedItem += (s, a) => { _scenarioList!.SetFocus (); };
|
|
categoryList.SelectedItemChanged += CategoryView_SelectedChanged;
|
|
|
|
// This enables the scrollbar by causing lazy instantiation to happen
|
|
categoryList.VerticalScrollBar.AutoShow = true;
|
|
|
|
return categoryList;
|
|
}
|
|
|
|
private void CategoryView_SelectedChanged (object? sender, ListViewItemEventArgs? e)
|
|
{
|
|
if (e is null or { Item: null })
|
|
{
|
|
return;
|
|
}
|
|
string item = CachedCategories! [e.Item.Value];
|
|
ObservableCollection<Scenario> newScenarioList;
|
|
|
|
if (e.Item == 0)
|
|
{
|
|
// First category is "All"
|
|
newScenarioList = CachedScenarios!;
|
|
}
|
|
else
|
|
{
|
|
newScenarioList = new (CachedScenarios!.Where (s => s.GetCategories ().Contains (item)).ToList ());
|
|
}
|
|
|
|
_scenarioList.Table = new EnumerableTableSource<Scenario> (
|
|
newScenarioList,
|
|
new ()
|
|
{
|
|
{ "Name", s => s.GetName () }, { "Description", s => s.GetDescription () }
|
|
}
|
|
);
|
|
|
|
}
|
|
|
|
#endregion Category List
|
|
|
|
#region StatusBar
|
|
|
|
private readonly StatusBar? _statusBar;
|
|
|
|
[ConfigurationProperty (Scope = typeof (AppSettingsScope), OmitClassName = true)]
|
|
[JsonPropertyName ("UICatalog.StatusBar")]
|
|
public static bool ShowStatusBar { get; set; } = true;
|
|
|
|
private Shortcut? _shQuit;
|
|
private Shortcut? _shVersion;
|
|
private CheckBox? _force16ColorsShortcutCb;
|
|
|
|
private StatusBar CreateStatusBar ()
|
|
{
|
|
StatusBar statusBar = new ()
|
|
{
|
|
Visible = ShowStatusBar,
|
|
AlignmentModes = AlignmentModes.IgnoreFirstOrLast,
|
|
CanFocus = false
|
|
};
|
|
|
|
// ReSharper disable All
|
|
statusBar.Height = Dim.Auto (
|
|
DimAutoStyle.Auto,
|
|
minimumContentDim: Dim.Func (_ => statusBar.Visible ? 1 : 0),
|
|
maximumContentDim: Dim.Func (_ => statusBar.Visible ? 1 : 0));
|
|
// ReSharper restore All
|
|
|
|
_shQuit = new ()
|
|
{
|
|
CanFocus = false,
|
|
Title = "Quit",
|
|
Key = Application.QuitKey
|
|
};
|
|
|
|
_shVersion = new ()
|
|
{
|
|
Title = "Version Info",
|
|
CanFocus = false
|
|
};
|
|
|
|
var statusBarShortcut = new Shortcut
|
|
{
|
|
Key = Key.F10,
|
|
Title = "Show/Hide Status Bar",
|
|
CanFocus = false
|
|
};
|
|
|
|
statusBarShortcut.Accepting += (sender, args) =>
|
|
{
|
|
statusBar.Visible = !_statusBar!.Visible;
|
|
args.Handled = true;
|
|
};
|
|
|
|
_force16ColorsShortcutCb = new ()
|
|
{
|
|
Title = "16 color mode",
|
|
CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
|
|
CanFocus = false
|
|
};
|
|
|
|
_force16ColorsShortcutCb.CheckedStateChanging += (sender, args) =>
|
|
{
|
|
if (Application.Force16Colors
|
|
&& args.Result == CheckState.UnChecked
|
|
&& !Application.Driver!.SupportsTrueColor)
|
|
{
|
|
// If the driver does not support TrueColor, we cannot disable 16 colors
|
|
args.Handled = true;
|
|
}
|
|
};
|
|
|
|
_force16ColorsShortcutCb.CheckedStateChanged += (sender, args) =>
|
|
{
|
|
Application.Force16Colors = args.Value == CheckState.Checked;
|
|
_force16ColorsMenuItemCb!.CheckedState = args.Value;
|
|
Application.LayoutAndDraw ();
|
|
};
|
|
|
|
statusBar.Add (
|
|
_shQuit,
|
|
statusBarShortcut,
|
|
new Shortcut
|
|
{
|
|
CanFocus = false,
|
|
CommandView = _force16ColorsShortcutCb,
|
|
HelpText = "",
|
|
BindKeyToApplication = true,
|
|
Key = Key.F7
|
|
},
|
|
_shVersion
|
|
);
|
|
|
|
if (UICatalog.Options.DontEnableConfigurationManagement)
|
|
{
|
|
statusBar.AddShortcutAt (statusBar.SubViews.ToList ().IndexOf (_shVersion), new Shortcut () { Title = "CM is Disabled" });
|
|
}
|
|
|
|
return statusBar;
|
|
}
|
|
|
|
#endregion StatusBar
|
|
|
|
#region Configuration Manager
|
|
|
|
/// <summary>
|
|
/// Called when CM has applied changes.
|
|
/// </summary>
|
|
private void ConfigApplied ()
|
|
{
|
|
UpdateThemesMenu ();
|
|
|
|
SchemeName = CachedTopLevelScheme;
|
|
|
|
if (_shQuit is { })
|
|
{
|
|
_shQuit.Key = Application.QuitKey;
|
|
}
|
|
|
|
if (_statusBar is { })
|
|
{
|
|
_statusBar.Visible = ShowStatusBar;
|
|
}
|
|
|
|
_disableMouseCb!.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked;
|
|
_force16ColorsShortcutCb!.CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
|
|
|
|
Application.Current?.SetNeedsDraw ();
|
|
}
|
|
|
|
private void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) { ConfigApplied (); }
|
|
|
|
#endregion Configuration Manager
|
|
|
|
/// <summary>
|
|
/// Gets the message displayed in the About Box. `public` so it can be used from Unit tests.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static string GetAboutBoxMessage ()
|
|
{
|
|
// NOTE: Do not use multiline verbatim strings here.
|
|
// WSL gets all confused.
|
|
StringBuilder msg = new ();
|
|
msg.AppendLine ("UI Catalog: A comprehensive sample library and test app for");
|
|
msg.AppendLine ();
|
|
|
|
msg.AppendLine (
|
|
"""
|
|
_______ _ _ _____ _
|
|
|__ __| (_) | | / ____| (_)
|
|
| | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _
|
|
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
|
|
| | __/ | | | | | | | | | | | (_| | || |__| | |_| | |
|
|
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
|
|
""");
|
|
msg.AppendLine ();
|
|
msg.AppendLine ("v2 - Pre-Alpha");
|
|
msg.AppendLine ();
|
|
msg.AppendLine ("https://github.com/gui-cs/Terminal.Gui");
|
|
|
|
return msg.ToString ();
|
|
}
|
|
|
|
public static void OpenUrl (string url)
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
url = url.Replace ("&", "^&");
|
|
Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux))
|
|
{
|
|
using var process = new Process
|
|
{
|
|
StartInfo = new ()
|
|
{
|
|
FileName = "xdg-open",
|
|
Arguments = url,
|
|
RedirectStandardError = true,
|
|
RedirectStandardOutput = true,
|
|
CreateNoWindow = true,
|
|
UseShellExecute = false
|
|
}
|
|
};
|
|
process.Start ();
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
|
|
{
|
|
Process.Start ("open", url);
|
|
}
|
|
}
|
|
}
|