Files
Terminal.Gui/Examples/UICatalog/UICatalogTop.cs
Tig a6258ed398 Updates IListDataSource.Render to rename the start parameter to viewportXOffset (#4392)
* 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>
2025-11-19 20:39:34 -05:00

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);
}
}
}