mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 17:57:57 +01:00
475 lines
19 KiB
C#
475 lines
19 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>
|
|
/// The index of the selected button, or -1 if the user pressed ESC to close the dialog. This is useful for web
|
|
/// based console where by default there is no SynchronizationContext or TaskScheduler.
|
|
/// </summary>
|
|
public static int Clicked { get; private set; } = -1;
|
|
|
|
/// <summary>
|
|
/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via
|
|
/// <see cref="ConfigurationManager"/>.
|
|
/// </summary>
|
|
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
|
|
public static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single;
|
|
|
|
/// <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 ESC to close the dialog.</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="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 ESC to close the dialog.</returns>
|
|
/// <param name="title">Title for the query.</param>
|
|
/// <param name="message">Message to display, might contain multiple lines.</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 to show
|
|
/// to the user.
|
|
/// </summary>
|
|
/// <returns>The index of the selected button, or -1 if the user pressed ESC to close the dialog.</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="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 ESC to close the dialog.</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="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 ESC to close the dialog.</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="wrapMessagge">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 wrapMessagge = true,
|
|
params string [] buttons
|
|
)
|
|
{
|
|
return QueryFull (true, width, height, title, message, defaultButton, wrapMessagge, 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 ESC to close the dialog.</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="wrapMessagge">If wrap the message or not.</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 wrapMessagge = true,
|
|
params string [] buttons
|
|
)
|
|
{
|
|
return QueryFull (true, 0, 0, title, message, defaultButton, wrapMessagge, buttons);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Presents a normal <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 ESC to close the dialog.</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="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 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 ESC to close the dialog.</returns>
|
|
/// <param name="title">Title for the query.</param>
|
|
/// <param name="message">Message to display, might contain multiple lines.</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, params string [] buttons) { return QueryFull (false, 0, 0, title, message, 0, true, buttons); }
|
|
|
|
/// <summary>
|
|
/// Presents a normal <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 ESC to close the dialog.</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="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,
|
|
params string [] buttons
|
|
)
|
|
{
|
|
return QueryFull (false, 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 ESC to close the dialog.</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="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 normal <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 ESC to close the dialog.</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="wrapMessagge">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 wrapMessagge = true,
|
|
params string [] buttons
|
|
)
|
|
{
|
|
return QueryFull (false, width, height, title, message, defaultButton, wrapMessagge, 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 ESC to close the dialog.</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>
|
|
/// <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,
|
|
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++;
|
|
}
|
|
}
|
|
|
|
Dialog d;
|
|
|
|
d = new Dialog
|
|
{
|
|
Buttons = buttonList.ToArray (),
|
|
Title = title,
|
|
BorderStyle = DefaultBorderStyle,
|
|
Width = Dim.Percent (60),
|
|
Height = 5 // Border + one line of text + vspace + buttons
|
|
};
|
|
|
|
if (width != 0)
|
|
{
|
|
d.Width = width;
|
|
}
|
|
|
|
if (height != 0)
|
|
{
|
|
d.Height = height;
|
|
}
|
|
|
|
if (useErrorColors)
|
|
{
|
|
d.ColorScheme = Colors.ColorSchemes ["Error"];
|
|
}
|
|
else
|
|
{
|
|
d.ColorScheme = Colors.ColorSchemes ["Dialog"];
|
|
}
|
|
|
|
var messageLabel = new Label
|
|
{
|
|
AutoSize = !wrapMessage,
|
|
Text = message,
|
|
TextAlignment = TextAlignment.Centered,
|
|
X = Pos.Center (),
|
|
Y = 0
|
|
};
|
|
|
|
if (!messageLabel.AutoSize)
|
|
{
|
|
messageLabel.Width = Dim.Fill ();
|
|
messageLabel.Height = Dim.Fill (1);
|
|
}
|
|
|
|
messageLabel.TextFormatter.WordWrap = wrapMessage;
|
|
messageLabel.TextFormatter.MultiLine = !wrapMessage;
|
|
d.Add (messageLabel);
|
|
|
|
// 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 ();
|
|
}
|
|
}
|
|
|
|
d.Loaded += Dialog_Loaded;
|
|
|
|
// Run the modal; do not shutdown the mainloop driver when done
|
|
Application.Run (d);
|
|
d.Dispose ();
|
|
|
|
return Clicked;
|
|
|
|
void Dialog_Loaded (object s, EventArgs e)
|
|
{
|
|
if (width != 0 || height != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: replace with Dim.Fit when implemented
|
|
Rectangle maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
|
|
|
|
Thickness adornmentsThickness = d.GetAdornmentsThickness ();
|
|
|
|
if (wrapMessage)
|
|
{
|
|
messageLabel.TextFormatter.Size = new (
|
|
maxBounds.Size.Width
|
|
- adornmentsThickness.Horizontal,
|
|
maxBounds.Size.Height
|
|
- adornmentsThickness.Vertical);
|
|
}
|
|
|
|
string msg = messageLabel.TextFormatter.Format ();
|
|
Size messageSize = messageLabel.TextFormatter.FormatAndGetSize ();
|
|
|
|
// Ensure the width fits the text + buttons
|
|
int newWidth = Math.Max (
|
|
width,
|
|
Math.Max (
|
|
messageSize.Width + adornmentsThickness.Horizontal,
|
|
d.GetButtonsWidth () + d.Buttons.Length + adornmentsThickness.Horizontal));
|
|
|
|
if (newWidth > d.Frame.Width)
|
|
{
|
|
d.Width = newWidth;
|
|
}
|
|
|
|
// Ensure height fits the text + vspace + buttons
|
|
if (messageSize.Height == 0)
|
|
{
|
|
d.Height = Math.Max (height, 3 + adornmentsThickness.Vertical);
|
|
}
|
|
else
|
|
{
|
|
string lastLine = messageLabel.TextFormatter.GetLines () [^1];
|
|
|
|
// INTENT: Instead of the check against \n or \r\n, how about just Environment.NewLine?
|
|
d.Height = Math.Max (
|
|
height,
|
|
messageSize.Height
|
|
+ (lastLine.EndsWith ("\r\n") || lastLine.EndsWith ('\n') ? 1 : 2)
|
|
+ adornmentsThickness.Vertical);
|
|
}
|
|
|
|
d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame);
|
|
d.LayoutSubviews ();
|
|
}
|
|
}
|
|
}
|