Files
Terminal.Gui/Terminal.Gui/Views/MessageBox.cs
BDisp 63e75b7413 Fixes #3109. AOT support with .Net 8. (#3638)
* Add a native AOT project.

* Fixes Text.Json to work with native AOT.

* Fix silent errors on unit tests when testing the Red color which has a length of 3.

* Allowing test custom configuration without the config.json file match the unit tests configurations.

* Fix unit test if tested alone.

* Add native project into solution.

* Fix merge errors.

* Setting ConfigurationManager.ThrowOnJsonErrors as true to throw any serialization issue when published file runs.

* Remove unnecessary using's.

* Added unit test to ensure all serialization is properly configured.

* Fix warnings.

* Remove ThrowOnJsonErrors.

* Fix warnings.

---------

Co-authored-by: Tig <tig@users.noreply.github.com>
2024-08-06 12:05:36 -06:00

436 lines
20 KiB
C#

namespace Terminal.Gui;
/// <summary>
/// MessageBox displays a modal message to the user, with a title, a message and a series of options that the user
/// can choose from.
/// </summary>
/// <para>
/// The difference between the <see cref="Query(string, string, string[])"/> and
/// <see cref="ErrorQuery(string, string, string[])"/> method is the default set of colors used for the message box.
/// </para>
/// <para>
/// The following example pops up a <see cref="MessageBox"/> with the specified title and text, plus two
/// <see cref="Button"/>s. The value -1 is returned when the user cancels the <see cref="MessageBox"/> by pressing the
/// ESC key.
/// </para>
/// <example>
/// <code lang="c#">
/// var n = MessageBox.Query ("Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
/// if (n == 0)
/// quit = true;
/// else
/// quit = false;
/// </code>
/// </example>
public static class MessageBox
{
/// <summary>
/// Defines the default border styling for <see cref="MessageBox"/>. Can be configured via
/// <see cref="ConfigurationManager"/>.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; // Default is set in config.json
/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
/// <remarks>This property can be set in a Theme.</remarks>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static Alignment DefaultButtonAlignment { get; set; } = Alignment.Center; // Default is set in config.json
/// <summary>
/// Defines the default minimum MessageBox width, as a percentage of the screen width. Can be configured via
/// <see cref="ConfigurationManager"/>.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static int DefaultMinimumWidth { get; set; } = 0;
/// <summary>
/// Defines the default minimum Dialog height, as a percentage of the screen width. Can be configured via
/// <see cref="ConfigurationManager"/>.
/// </summary>
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static int DefaultMinimumHeight { get; set; } = 0;
/// <summary>
/// The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox. This is useful for web
/// based console where there is no SynchronizationContext or TaskScheduler.
/// </summary>
/// <remarks>
/// Warning: This is a global variable and should be used with caution. It is not thread safe.
/// </remarks>
public static int Clicked { get; private set; } = -1;
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the MessageBox.</param>
/// <param name="height">Height for the MessageBox.</param>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </remarks>
public static int ErrorQuery (int width, int height, string title, string message, params string [] buttons)
{
return QueryFull (true, width, height, title, message, 0, true, buttons);
}
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the query.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the title, message. and buttons.
/// </remarks>
public static int ErrorQuery (string title, string message, params string [] buttons) { return QueryFull (true, 0, 0, title, message, 0, true, buttons); }
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the MessageBox.</param>
/// <param name="height">Height for the MessageBox.</param>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </remarks>
public static int ErrorQuery (
int width,
int height,
string title,
string message,
int defaultButton = 0,
params string [] buttons
)
{
return QueryFull (true, width, height, title, message, defaultButton, true, buttons);
}
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the title, message. and buttons.
/// </remarks>
public static int ErrorQuery (string title, string message, int defaultButton = 0, params string [] buttons)
{
return QueryFull (true, 0, 0, title, message, defaultButton, true, buttons);
}
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the window.</param>
/// <param name="height">Height for the window.</param>
/// <param name="title">Title for the query.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="wrapMessage">If wrap the message or not.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// Use <see cref="ErrorQuery(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </remarks>
public static int ErrorQuery (
int width,
int height,
string title,
string message,
int defaultButton = 0,
bool wrapMessage = true,
params string [] buttons
)
{
return QueryFull (true, width, height, title, message, defaultButton, wrapMessage, buttons);
}
/// <summary>
/// Presents an error <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the query.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="wrapMessage">If wrap the message or not. The default is <see langword="true"/></param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the title, message. and buttons.
/// </remarks>
public static int ErrorQuery (
string title,
string message,
int defaultButton = 0,
bool wrapMessage = true,
params string [] buttons
)
{
return QueryFull (true, 0, 0, title, message, defaultButton, wrapMessage, buttons);
}
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the MessageBox.</param>
/// <param name="height">Height for the MessageBox.</param>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </remarks>
public static int Query (int width, int height, string title, string message, params string [] buttons)
{
return QueryFull (false, width, height, title, message, 0, true, buttons);
}
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// <para>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the title, message. and buttons.
/// </para>
/// <para>
/// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </para>
/// </remarks>
public static int Query (string title, string message, params string [] buttons) { return QueryFull (false, 0, 0, title, message, 0, true, buttons); }
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the window.</param>
/// <param name="height">Height for the window.</param>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// <para>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the title, message. and buttons.
/// </para>
/// <para>
/// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on
/// the contents.
/// </para>
/// </remarks>
public static int Query (
int width,
int height,
string title,
string message,
int defaultButton = 0,
params string [] buttons
)
{
return QueryFull (false, width, height, title, message, defaultButton, true, buttons);
}
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the MessageBox.</param>
/// <param name="message">Message to display; might contain multiple lines. The message will be word=wrapped by default.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// The message box will be vertically and horizontally centered in the container and the size will be
/// automatically determined from the size of the message and buttons.
/// </remarks>
public static int Query (string title, string message, int defaultButton = 0, params string [] buttons)
{
return QueryFull (false, 0, 0, title, message, defaultButton, true, buttons);
}
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="width">Width for the window.</param>
/// <param name="height">Height for the window.</param>
/// <param name="title">Title for the query.</param>
/// <param name="message">Message to display, might contain multiple lines.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="wrapMessage">If wrap the message or not.</param>
/// <param name="buttons">Array of buttons to add.</param>
/// <remarks>
/// Use <see cref="Query(string, string, string[])"/> instead; it automatically sizes the MessageBox based on the
/// contents.
/// </remarks>
public static int Query (
int width,
int height,
string title,
string message,
int defaultButton = 0,
bool wrapMessage = true,
params string [] buttons
)
{
return QueryFull (false, width, height, title, message, defaultButton, wrapMessage, buttons);
}
/// <summary>
/// Presents a <see cref="MessageBox"/> with the specified title and message and a list of buttons to show
/// to the user.
/// </summary>
/// <returns>The index of the selected button, or -1 if the user pressed <see cref="Application.QuitKey"/> to close the MessageBox.</returns>
/// <param name="title">Title for the query.</param>
/// <param name="message">Message to display, might contain multiple lines.</param>
/// <param name="defaultButton">Index of the default button.</param>
/// <param name="wrapMessage">If wrap the message or not.</param>
/// <param name="buttons">Array of buttons to add.</param>
public static int Query (
string title,
string message,
int defaultButton = 0,
bool wrapMessage = true,
params string [] buttons
)
{
return QueryFull (false, 0, 0, title, message, defaultButton, wrapMessage, buttons);
}
private static int QueryFull (
bool useErrorColors,
int width,
int height,
string title,
string message,
int defaultButton = 0,
bool wrapMessage = true,
params string [] buttons
)
{
// Create button array for Dialog
var count = 0;
List<Button> buttonList = new ();
if (buttons is { })
{
if (defaultButton > buttons.Length - 1)
{
defaultButton = buttons.Length - 1;
}
foreach (string s in buttons)
{
var b = new Button
{
Text = s,
};
if (count == defaultButton)
{
b.IsDefault = true;
}
buttonList.Add (b);
count++;
}
}
var d = new Dialog
{
Title = title,
ButtonAlignment = MessageBox.DefaultButtonAlignment,
ButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems,
BorderStyle = MessageBox.DefaultBorderStyle,
Buttons = buttonList.ToArray (),
};
d.Width = Dim.Auto (DimAutoStyle.Auto,
minimumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * (DefaultMinimumWidth / 100f) )),
maximumContentDim: Dim.Func (() => (int)((Application.Screen.Width - d.GetAdornmentsThickness ().Horizontal) * 0.9f)));
d.Height = Dim.Auto (DimAutoStyle.Auto,
minimumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * (DefaultMinimumHeight / 100f))),
maximumContentDim: Dim.Func (() => (int)((Application.Screen.Height - d.GetAdornmentsThickness ().Vertical) * 0.9f)));
if (width != 0)
{
d.Width = width;
}
if (height != 0)
{
d.Height = height;
}
d.ColorScheme = useErrorColors ? Colors.ColorSchemes ["Error"] : Colors.ColorSchemes ["Dialog"];
d.HotKeySpecifier = new Rune ('\xFFFF');
d.Text = message;
d.TextAlignment = Alignment.Center;
d.VerticalTextAlignment = Alignment.Start;
d.TextFormatter.WordWrap = wrapMessage;
d.TextFormatter.MultiLine = !wrapMessage;
d.ColorScheme = new ColorScheme (d.ColorScheme)
{
Focus = d.ColorScheme.Normal
};
// Setup actions
Clicked = -1;
for (var n = 0; n < buttonList.Count; n++)
{
int buttonId = n;
Button b = buttonList [n];
b.Accept += (s, e) =>
{
Clicked = buttonId;
Application.RequestStop ();
};
if (b.IsDefault)
{
b.SetFocus ();
}
}
// Run the modal; do not shut down the mainloop driver when done
Application.Run (d);
d.Dispose ();
return Clicked;
}
}