mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 15:57:45 +00:00
LibGUI+Userland: Convert MessageBox to fallible construction
Adds fallible versions of MessageBox's helper factories, ports DeprecatedString, and rewrites build() to be both fallible and more responsive to font changes. MessageBox now auto shrinks and no longer has to re-build its layout when text changes. It is manually resized once at creation to position properly as a Dialog before being shown.
This commit is contained in:
parent
aa94b944de
commit
c9404c3a63
4 changed files with 92 additions and 83 deletions
|
@ -265,8 +265,9 @@ void MonitorSettingsWidget::apply_settings()
|
||||||
return;
|
return;
|
||||||
auto current_box_text = current_box_text_or_error.release_value();
|
auto current_box_text = current_box_text_or_error.release_value();
|
||||||
|
|
||||||
auto box = GUI::MessageBox::construct(window(), current_box_text, "Apply new screen layout"sv,
|
auto box = GUI::MessageBox::create(window(), current_box_text, "Apply new screen layout"sv,
|
||||||
GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
|
GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo)
|
||||||
|
.release_value_but_fixme_should_propagate_errors();
|
||||||
box->set_icon(window()->icon());
|
box->set_icon(window()->icon());
|
||||||
|
|
||||||
// If after 10 seconds the user doesn't close the message box, just close it.
|
// If after 10 seconds the user doesn't close the message box, just close it.
|
||||||
|
@ -279,7 +280,7 @@ void MonitorSettingsWidget::apply_settings()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto current_box_text = current_box_text_or_error.release_value();
|
auto current_box_text = current_box_text_or_error.release_value();
|
||||||
box->set_text(current_box_text.to_deprecated_string());
|
box->set_text(current_box_text);
|
||||||
if (seconds_until_revert <= 0) {
|
if (seconds_until_revert <= 0) {
|
||||||
box->close();
|
box->close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,29 @@
|
||||||
#include <LibGUI/ImageWidget.h>
|
#include <LibGUI/ImageWidget.h>
|
||||||
#include <LibGUI/Label.h>
|
#include <LibGUI/Label.h>
|
||||||
#include <LibGUI/MessageBox.h>
|
#include <LibGUI/MessageBox.h>
|
||||||
#include <LibGfx/Font/Font.h>
|
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
ErrorOr<NonnullRefPtr<MessageBox>> MessageBox::create(Window* parent_window, StringView text, StringView title, Type type, InputType input_type)
|
||||||
|
{
|
||||||
|
auto box = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) MessageBox(parent_window, type, input_type)));
|
||||||
|
TRY(box->build());
|
||||||
|
box->set_title(TRY(String::from_utf8(title)).to_deprecated_string());
|
||||||
|
box->set_text(TRY(String::from_utf8(text)));
|
||||||
|
auto size = box->main_widget()->effective_min_size();
|
||||||
|
box->resize(TRY(size.width().shrink_value()), TRY(size.height().shrink_value()));
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
Dialog::ExecResult MessageBox::show(Window* parent_window, StringView text, StringView title, Type type, InputType input_type)
|
Dialog::ExecResult MessageBox::show(Window* parent_window, StringView text, StringView title, Type type, InputType input_type)
|
||||||
{
|
{
|
||||||
auto box = MessageBox::construct(parent_window, text, title, type, input_type);
|
return MUST(try_show(parent_window, text, title, type, input_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<Dialog::ExecResult> MessageBox::try_show(Window* parent_window, StringView text, StringView title, Type type, InputType input_type)
|
||||||
|
{
|
||||||
|
auto box = TRY(MessageBox::create(parent_window, text, title, type, input_type));
|
||||||
if (parent_window)
|
if (parent_window)
|
||||||
box->set_icon(parent_window->icon());
|
box->set_icon(parent_window->icon());
|
||||||
return box->exec();
|
return box->exec();
|
||||||
|
@ -26,31 +42,41 @@ Dialog::ExecResult MessageBox::show(Window* parent_window, StringView text, Stri
|
||||||
|
|
||||||
Dialog::ExecResult MessageBox::show_error(Window* parent_window, StringView text)
|
Dialog::ExecResult MessageBox::show_error(Window* parent_window, StringView text)
|
||||||
{
|
{
|
||||||
return show(parent_window, text, "Error"sv, GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK);
|
return MUST(try_show_error(parent_window, text));
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<Dialog::ExecResult> MessageBox::try_show_error(Window* parent_window, StringView text)
|
||||||
|
{
|
||||||
|
return TRY(try_show(parent_window, text, "Error"sv, GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK));
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog::ExecResult MessageBox::ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp)
|
Dialog::ExecResult MessageBox::ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp)
|
||||||
|
{
|
||||||
|
return MUST(try_ask_about_unsaved_changes(parent_window, path, last_unmodified_timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<Dialog::ExecResult> MessageBox::try_ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp)
|
||||||
{
|
{
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.append("Save changes to "sv);
|
TRY(builder.try_append("Save changes to "sv));
|
||||||
if (path.is_empty())
|
if (path.is_empty())
|
||||||
builder.append("untitled document"sv);
|
TRY(builder.try_append("untitled document"sv));
|
||||||
else
|
else
|
||||||
builder.appendff("\"{}\"", LexicalPath::basename(path));
|
TRY(builder.try_appendff("\"{}\"", LexicalPath::basename(path)));
|
||||||
builder.append(" before closing?"sv);
|
TRY(builder.try_append(" before closing?"sv));
|
||||||
|
|
||||||
if (!path.is_empty() && last_unmodified_timestamp.has_value()) {
|
if (!path.is_empty() && last_unmodified_timestamp.has_value()) {
|
||||||
auto age = (Time::now_monotonic() - *last_unmodified_timestamp).to_seconds();
|
auto age = (Time::now_monotonic() - *last_unmodified_timestamp).to_seconds();
|
||||||
auto readable_time = human_readable_time(age);
|
auto readable_time = human_readable_time(age);
|
||||||
builder.appendff("\nLast saved {} ago.", readable_time);
|
TRY(builder.try_appendff("\nLast saved {} ago.", readable_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto box = MessageBox::construct(parent_window, builder.string_view(), "Unsaved changes"sv, Type::Warning, InputType::YesNoCancel);
|
auto box = TRY(MessageBox::create(parent_window, builder.string_view(), "Unsaved changes"sv, Type::Warning, InputType::YesNoCancel));
|
||||||
if (parent_window)
|
if (parent_window)
|
||||||
box->set_icon(parent_window->icon());
|
box->set_icon(parent_window->icon());
|
||||||
|
|
||||||
if (path.is_empty())
|
if (path.is_empty())
|
||||||
box->m_yes_button->set_text("Save As..."_string.release_value_but_fixme_should_propagate_errors());
|
box->m_yes_button->set_text(TRY("Save As..."_string));
|
||||||
else
|
else
|
||||||
box->m_yes_button->set_text("Save"_short_string);
|
box->m_yes_button->set_text("Save"_short_string);
|
||||||
box->m_no_button->set_text("Discard"_short_string);
|
box->m_no_button->set_text("Discard"_short_string);
|
||||||
|
@ -59,33 +85,31 @@ Dialog::ExecResult MessageBox::ask_about_unsaved_changes(Window* parent_window,
|
||||||
return box->exec();
|
return box->exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageBox::set_text(DeprecatedString text)
|
void MessageBox::set_text(String text)
|
||||||
{
|
{
|
||||||
m_text = move(text);
|
m_text_label->set_text(move(text).to_deprecated_string());
|
||||||
build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageBox::MessageBox(Window* parent_window, StringView text, StringView title, Type type, InputType input_type)
|
MessageBox::MessageBox(Window* parent_window, Type type, InputType input_type)
|
||||||
: Dialog(parent_window)
|
: Dialog(parent_window)
|
||||||
, m_text(text)
|
|
||||||
, m_type(type)
|
, m_type(type)
|
||||||
, m_input_type(input_type)
|
, m_input_type(input_type)
|
||||||
{
|
{
|
||||||
set_title(title);
|
set_resizable(false);
|
||||||
build();
|
set_auto_shrink(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<Gfx::Bitmap> MessageBox::icon() const
|
ErrorOr<RefPtr<Gfx::Bitmap>> MessageBox::icon() const
|
||||||
{
|
{
|
||||||
switch (m_type) {
|
switch (m_type) {
|
||||||
case Type::Information:
|
case Type::Information:
|
||||||
return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-information.png"sv).release_value_but_fixme_should_propagate_errors();
|
return TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-information.png"sv));
|
||||||
case Type::Warning:
|
case Type::Warning:
|
||||||
return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-warning.png"sv).release_value_but_fixme_should_propagate_errors();
|
return TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-warning.png"sv));
|
||||||
case Type::Error:
|
case Type::Error:
|
||||||
return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-error.png"sv).release_value_but_fixme_should_propagate_errors();
|
return TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-error.png"sv));
|
||||||
case Type::Question:
|
case Type::Question:
|
||||||
return Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-question.png"sv).release_value_but_fixme_should_propagate_errors();
|
return TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/msgbox-question.png"sv));
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -111,72 +135,49 @@ bool MessageBox::should_include_no_button() const
|
||||||
return should_include_yes_button();
|
return should_include_yes_button();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageBox::build()
|
ErrorOr<void> MessageBox::build()
|
||||||
{
|
{
|
||||||
auto widget = set_main_widget<Widget>().release_value_but_fixme_should_propagate_errors();
|
auto main_widget = TRY(set_main_widget<Widget>());
|
||||||
|
main_widget->set_fill_with_background_color(true);
|
||||||
|
TRY(main_widget->try_set_layout<VerticalBoxLayout>(8, 6));
|
||||||
|
|
||||||
int text_width = widget->font().width(m_text);
|
auto message_container = TRY(main_widget->try_add<Widget>());
|
||||||
auto number_of_lines = m_text.split('\n').size();
|
auto message_margins = Margins { 8, m_type != Type::None ? 8 : 0 };
|
||||||
int padded_text_height = widget->font().pixel_size_rounded_up() * 1.6;
|
TRY(message_container->try_set_layout<HorizontalBoxLayout>(message_margins, 8));
|
||||||
int total_text_height = number_of_lines * padded_text_height;
|
|
||||||
int icon_width = 0;
|
|
||||||
|
|
||||||
widget->set_layout<VerticalBoxLayout>(8, 6);
|
if (auto icon = TRY(this->icon()); icon && m_type != Type::None) {
|
||||||
widget->set_fill_with_background_color(true);
|
auto image_widget = TRY(message_container->try_add<ImageWidget>());
|
||||||
|
image_widget->set_bitmap(icon);
|
||||||
auto& message_container = widget->add<Widget>();
|
|
||||||
message_container.set_layout<HorizontalBoxLayout>(GUI::Margins {}, 8);
|
|
||||||
|
|
||||||
if (m_type != Type::None) {
|
|
||||||
auto& icon_image = message_container.add<ImageWidget>();
|
|
||||||
icon_image.set_bitmap(icon());
|
|
||||||
if (icon()) {
|
|
||||||
icon_width = icon()->width();
|
|
||||||
if (icon_width > 0)
|
|
||||||
message_container.layout()->set_margins({ 0, 0, 0, 8 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& label = message_container.add<Label>(m_text);
|
m_text_label = TRY(message_container->try_add<Label>());
|
||||||
label.set_fixed_height(total_text_height);
|
m_text_label->set_text_wrapping(Gfx::TextWrapping::DontWrap);
|
||||||
|
m_text_label->set_autosize(true);
|
||||||
if (m_type != Type::None)
|
if (m_type != Type::None)
|
||||||
label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
m_text_label->set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||||
|
|
||||||
auto& button_container = widget->add<Widget>();
|
auto button_container = TRY(main_widget->try_add<Widget>());
|
||||||
button_container.set_layout<HorizontalBoxLayout>(GUI::Margins {}, 8);
|
TRY(button_container->try_set_layout<HorizontalBoxLayout>(Margins {}, 8));
|
||||||
button_container.set_fixed_height(24);
|
|
||||||
|
|
||||||
constexpr int button_width = 80;
|
auto add_button = [&](String text, ExecResult result) -> ErrorOr<NonnullRefPtr<Button>> {
|
||||||
int button_count = 0;
|
auto button = TRY(button_container->try_add<DialogButton>());
|
||||||
|
button->set_text(move(text));
|
||||||
auto add_button = [&](String label, ExecResult result) -> GUI::Button& {
|
button->on_click = [this, result](auto) { done(result); };
|
||||||
auto& button = button_container.add<Button>();
|
|
||||||
button.set_fixed_width(button_width);
|
|
||||||
button.set_text(move(label));
|
|
||||||
button.on_click = [this, result](auto) {
|
|
||||||
done(result);
|
|
||||||
};
|
|
||||||
++button_count;
|
|
||||||
return button;
|
return button;
|
||||||
};
|
};
|
||||||
|
|
||||||
button_container.add_spacer().release_value_but_fixme_should_propagate_errors();
|
TRY(button_container->add_spacer());
|
||||||
if (should_include_ok_button())
|
if (should_include_ok_button())
|
||||||
m_ok_button = add_button("OK"_short_string, ExecResult::OK);
|
m_ok_button = TRY(add_button("OK"_short_string, ExecResult::OK));
|
||||||
if (should_include_yes_button())
|
if (should_include_yes_button())
|
||||||
m_yes_button = add_button("Yes"_short_string, ExecResult::Yes);
|
m_yes_button = TRY(add_button("Yes"_short_string, ExecResult::Yes));
|
||||||
if (should_include_no_button())
|
if (should_include_no_button())
|
||||||
m_no_button = add_button("No"_short_string, ExecResult::No);
|
m_no_button = TRY(add_button("No"_short_string, ExecResult::No));
|
||||||
if (should_include_cancel_button())
|
if (should_include_cancel_button())
|
||||||
m_cancel_button = add_button("Cancel"_short_string, ExecResult::Cancel);
|
m_cancel_button = TRY(add_button("Cancel"_short_string, ExecResult::Cancel));
|
||||||
button_container.add_spacer().release_value_but_fixme_should_propagate_errors();
|
TRY(button_container->add_spacer());
|
||||||
|
|
||||||
int width = (button_count * button_width) + ((button_count - 1) * button_container.layout()->spacing()) + 32;
|
return {};
|
||||||
width = max(width, text_width + icon_width + 56);
|
|
||||||
|
|
||||||
// FIXME: Use shrink from new layout system
|
|
||||||
set_rect(x(), y(), width, 80 + label.text_calculated_preferred_height());
|
|
||||||
set_resizable(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
class MessageBox : public Dialog {
|
class MessageBox : public Dialog {
|
||||||
C_OBJECT(MessageBox)
|
C_OBJECT_ABSTRACT(MessageBox)
|
||||||
public:
|
public:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
None,
|
None,
|
||||||
|
@ -36,19 +36,25 @@ public:
|
||||||
static ExecResult show_error(Window* parent_window, StringView text);
|
static ExecResult show_error(Window* parent_window, StringView text);
|
||||||
static ExecResult ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp = {});
|
static ExecResult ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp = {});
|
||||||
|
|
||||||
void set_text(DeprecatedString text);
|
static ErrorOr<ExecResult> try_show(Window* parent_window, StringView text, StringView title, Type type = Type::None, InputType input_type = InputType::OK);
|
||||||
|
static ErrorOr<ExecResult> try_show_error(Window* parent_window, StringView text);
|
||||||
|
static ErrorOr<ExecResult> try_ask_about_unsaved_changes(Window* parent_window, StringView path, Optional<Time> last_unmodified_timestamp = {});
|
||||||
|
|
||||||
|
static ErrorOr<NonnullRefPtr<MessageBox>> create(Window* parent_window, StringView text, StringView title, Type type = Type::None, InputType input_type = InputType::OK);
|
||||||
|
|
||||||
|
void set_text(String);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit MessageBox(Window* parent_window, StringView text, StringView title, Type type = Type::None, InputType input_type = InputType::OK);
|
MessageBox(Window* parent_window, Type type = Type::None, InputType input_type = InputType::OK);
|
||||||
|
|
||||||
bool should_include_ok_button() const;
|
bool should_include_ok_button() const;
|
||||||
bool should_include_cancel_button() const;
|
bool should_include_cancel_button() const;
|
||||||
bool should_include_yes_button() const;
|
bool should_include_yes_button() const;
|
||||||
bool should_include_no_button() const;
|
bool should_include_no_button() const;
|
||||||
void build();
|
|
||||||
RefPtr<Gfx::Bitmap> icon() const;
|
|
||||||
|
|
||||||
DeprecatedString m_text;
|
ErrorOr<void> build();
|
||||||
|
ErrorOr<RefPtr<Gfx::Bitmap>> icon() const;
|
||||||
|
|
||||||
Type m_type { Type::None };
|
Type m_type { Type::None };
|
||||||
InputType m_input_type { InputType::OK };
|
InputType m_input_type { InputType::OK };
|
||||||
|
|
||||||
|
@ -56,6 +62,7 @@ private:
|
||||||
RefPtr<GUI::Button> m_yes_button;
|
RefPtr<GUI::Button> m_yes_button;
|
||||||
RefPtr<GUI::Button> m_no_button;
|
RefPtr<GUI::Button> m_no_button;
|
||||||
RefPtr<GUI::Button> m_cancel_button;
|
RefPtr<GUI::Button> m_cancel_button;
|
||||||
|
RefPtr<Label> m_text_label;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,7 +352,7 @@ void OutOfProcessWebView::notify_server_did_request_image_context_menu(Badge<Web
|
||||||
|
|
||||||
void OutOfProcessWebView::notify_server_did_request_alert(Badge<WebContentClient>, String const& message)
|
void OutOfProcessWebView::notify_server_did_request_alert(Badge<WebContentClient>, String const& message)
|
||||||
{
|
{
|
||||||
m_dialog = GUI::MessageBox::construct(window(), message, "Alert"sv, GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK);
|
m_dialog = GUI::MessageBox::create(window(), message, "Alert"sv, GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK).release_value_but_fixme_should_propagate_errors();
|
||||||
m_dialog->set_icon(window()->icon());
|
m_dialog->set_icon(window()->icon());
|
||||||
m_dialog->exec();
|
m_dialog->exec();
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ void OutOfProcessWebView::notify_server_did_request_alert(Badge<WebContentClient
|
||||||
|
|
||||||
void OutOfProcessWebView::notify_server_did_request_confirm(Badge<WebContentClient>, String const& message)
|
void OutOfProcessWebView::notify_server_did_request_confirm(Badge<WebContentClient>, String const& message)
|
||||||
{
|
{
|
||||||
m_dialog = GUI::MessageBox::construct(window(), message, "Confirm"sv, GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel);
|
m_dialog = GUI::MessageBox::create(window(), message, "Confirm"sv, GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::OKCancel).release_value_but_fixme_should_propagate_errors();
|
||||||
m_dialog->set_icon(window()->icon());
|
m_dialog->set_icon(window()->icon());
|
||||||
|
|
||||||
client().async_confirm_closed(m_dialog->exec() == GUI::Dialog::ExecResult::OK);
|
client().async_confirm_closed(m_dialog->exec() == GUI::Dialog::ExecResult::OK);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue