1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 18:28:12 +00:00
serenity/Userland/Applications/DisplaySettings/MonitorSettingsWidget.cpp
thankyouverycool c9404c3a63 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.
2023-04-15 15:24:50 +02:00

343 lines
15 KiB
C++

/*
* Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com>
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "MonitorSettingsWidget.h"
#include <Applications/DisplaySettings/MonitorSettingsGML.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/ComboBox.h>
#include <LibGUI/ConnectionToWindowServer.h>
#include <LibGUI/ItemListModel.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/RadioButton.h>
#include <LibGfx/SystemTheme.h>
namespace DisplaySettings {
ErrorOr<NonnullRefPtr<MonitorSettingsWidget>> MonitorSettingsWidget::try_create()
{
auto monitor_settings_widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) MonitorSettingsWidget()));
TRY(monitor_settings_widget->create_resolution_list());
TRY(monitor_settings_widget->create_frame());
TRY(monitor_settings_widget->load_current_settings());
return monitor_settings_widget;
}
ErrorOr<void> MonitorSettingsWidget::create_resolution_list()
{
// TODO: Find a better way to get the default resolution
TRY(m_resolutions.try_append({ 640, 480 }));
TRY(m_resolutions.try_append({ 800, 600 }));
TRY(m_resolutions.try_append({ 1024, 768 }));
TRY(m_resolutions.try_append({ 1280, 720 }));
TRY(m_resolutions.try_append({ 1280, 768 }));
TRY(m_resolutions.try_append({ 1280, 960 }));
TRY(m_resolutions.try_append({ 1280, 1024 }));
TRY(m_resolutions.try_append({ 1360, 768 }));
TRY(m_resolutions.try_append({ 1368, 768 }));
TRY(m_resolutions.try_append({ 1440, 900 }));
TRY(m_resolutions.try_append({ 1600, 900 }));
TRY(m_resolutions.try_append({ 1600, 1200 }));
TRY(m_resolutions.try_append({ 1920, 1080 }));
TRY(m_resolutions.try_append({ 2048, 1152 }));
TRY(m_resolutions.try_append({ 2256, 1504 }));
TRY(m_resolutions.try_append({ 2560, 1080 }));
TRY(m_resolutions.try_append({ 2560, 1440 }));
TRY(m_resolutions.try_append({ 3440, 1440 }));
for (auto resolution : m_resolutions) {
// Use Euclid's Algorithm to calculate greatest common factor
i32 a = resolution.width();
i32 b = resolution.height();
i32 gcf = 0;
for (;;) {
i32 r = a % b;
if (r == 0) {
gcf = b;
break;
}
a = b;
b = r;
}
i32 aspect_width = resolution.width() / gcf;
i32 aspect_height = resolution.height() / gcf;
TRY(m_resolution_strings.try_append(TRY(String::formatted("{}x{} ({}:{})", resolution.width(), resolution.height(), aspect_width, aspect_height))));
}
return {};
}
ErrorOr<void> MonitorSettingsWidget::create_frame()
{
TRY(load_from_gml(monitor_settings_window_gml));
m_monitor_widget = *find_descendant_of_type_named<DisplaySettings::MonitorWidget>("monitor_widget");
m_screen_combo = *find_descendant_of_type_named<GUI::ComboBox>("screen_combo");
m_screen_combo->set_only_allow_values_from_model(true);
m_screen_combo->set_model(*GUI::ItemListModel<String>::create(m_screens));
m_screen_combo->on_change = [this](auto&, const GUI::ModelIndex& index) {
m_selected_screen_index = index.row();
auto result = selected_screen_index_or_resolution_changed();
if (result.is_error())
GUI::MessageBox::show_error(window(), "Screen info could not be updated"sv);
};
m_resolution_combo = *find_descendant_of_type_named<GUI::ComboBox>("resolution_combo");
m_resolution_combo->set_only_allow_values_from_model(true);
m_resolution_combo->set_model(*GUI::ItemListModel<String>::create(m_resolution_strings));
m_resolution_combo->on_change = [this](auto&, const GUI::ModelIndex& index) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.resolution = m_resolutions.at(index.row());
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
auto result = selected_screen_index_or_resolution_changed();
if (result.is_error()) {
GUI::MessageBox::show_error(window(), "Screen info could not be updated"sv);
return;
}
set_modified(true);
};
m_display_scale_radio_1x = *find_descendant_of_type_named<GUI::RadioButton>("scale_1x");
m_display_scale_radio_1x->on_checked = [this](bool checked) {
if (checked) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.scale_factor = 1;
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
m_monitor_widget->set_desktop_scale_factor(1);
m_monitor_widget->update();
set_modified(true);
}
};
m_display_scale_radio_2x = *find_descendant_of_type_named<GUI::RadioButton>("scale_2x");
m_display_scale_radio_2x->on_checked = [this](bool checked) {
if (checked) {
auto& selected_screen = m_screen_layout.screens[m_selected_screen_index];
selected_screen.scale_factor = 2;
// Try to auto re-arrange things if there are overlaps or disconnected screens
m_screen_layout.normalize();
m_monitor_widget->set_desktop_scale_factor(2);
m_monitor_widget->update();
set_modified(true);
}
};
m_dpi_label = *find_descendant_of_type_named<GUI::Label>("display_dpi");
return {};
}
static ErrorOr<String> display_name_from_edid(EDID::Parser const& edid)
{
auto manufacturer_name = edid.manufacturer_name();
auto product_name = edid.display_product_name();
auto build_manufacturer_product_name = [&]() -> ErrorOr<String> {
if (product_name.is_null() || product_name.is_empty())
return TRY(String::from_deprecated_string(manufacturer_name));
return String::formatted("{} {}", manufacturer_name, product_name);
};
if (auto screen_size = edid.screen_size(); screen_size.has_value()) {
auto diagonal_inch = hypot(screen_size.value().horizontal_cm(), screen_size.value().vertical_cm()) / 2.54;
return String::formatted("{} {}\"", TRY(build_manufacturer_product_name()), roundf(diagonal_inch));
}
return build_manufacturer_product_name();
}
ErrorOr<void> MonitorSettingsWidget::load_current_settings()
{
m_screen_layout = GUI::ConnectionToWindowServer::the().get_screen_layout();
m_screens.clear();
m_screen_edids.clear();
size_t virtual_screen_count = 0;
for (size_t i = 0; i < m_screen_layout.screens.size(); i++) {
String screen_display_name;
if (m_screen_layout.screens[i].mode == WindowServer::ScreenLayout::Screen::Mode::Device) {
if (auto edid = EDID::Parser::from_display_connector_device(m_screen_layout.screens[i].device.value()); !edid.is_error()) { // TODO: multihead
screen_display_name = TRY(display_name_from_edid(edid.value()));
TRY(m_screen_edids.try_append(edid.release_value()));
} else {
dbgln("Error getting EDID from device {}: {}", m_screen_layout.screens[i].device.value(), edid.error());
screen_display_name = TRY(String::from_deprecated_string(m_screen_layout.screens[i].device.value()));
TRY(m_screen_edids.try_append({}));
}
} else {
dbgln("Frame buffer {} is virtual.", i);
screen_display_name = TRY(String::formatted("Virtual screen {}", virtual_screen_count++));
TRY(m_screen_edids.try_append({}));
}
if (i == m_screen_layout.main_screen_index)
TRY(m_screens.try_append(TRY(String::formatted("{}: {} (main screen)", i + 1, screen_display_name))));
else
TRY(m_screens.try_append(TRY(String::formatted("{}: {}", i + 1, screen_display_name))));
}
m_selected_screen_index = m_screen_layout.main_screen_index;
m_screen_combo->set_selected_index(m_selected_screen_index);
TRY(selected_screen_index_or_resolution_changed());
return {};
}
ErrorOr<void> MonitorSettingsWidget::selected_screen_index_or_resolution_changed()
{
auto& screen = m_screen_layout.screens[m_selected_screen_index];
// Let's attempt to find the current resolution based on the screen layout settings
auto index = m_resolutions.find_first_index(screen.resolution).value_or(0);
Gfx::IntSize current_resolution = m_resolutions.at(index);
Optional<unsigned> screen_dpi;
String screen_dpi_tooltip;
if (m_screen_edids[m_selected_screen_index].has_value()) {
auto& edid = m_screen_edids[m_selected_screen_index];
if (auto screen_size = edid.value().screen_size(); screen_size.has_value()) {
auto x_cm = screen_size.value().horizontal_cm();
auto y_cm = screen_size.value().vertical_cm();
auto diagonal_inch = hypot(x_cm, y_cm) / 2.54;
auto diagonal_pixels = hypot(current_resolution.width(), current_resolution.height());
if (diagonal_pixels != 0.0) {
screen_dpi = diagonal_pixels / diagonal_inch;
screen_dpi_tooltip = TRY(String::formatted("{} inch display ({}cm x {}cm)", roundf(diagonal_inch), x_cm, y_cm));
}
}
}
auto dpi_label_value = String::formatted("{} dpi", screen_dpi.value());
if (screen_dpi.has_value() && !dpi_label_value.is_error()) {
m_dpi_label->set_tooltip(screen_dpi_tooltip.to_deprecated_string());
m_dpi_label->set_text(dpi_label_value.release_value().to_deprecated_string());
m_dpi_label->set_visible(true);
} else {
m_dpi_label->set_visible(false);
}
if (screen.scale_factor != 1 && screen.scale_factor != 2) {
dbgln("unexpected ScaleFactor {}, setting to 1", screen.scale_factor);
screen.scale_factor = 1;
}
(screen.scale_factor == 1 ? m_display_scale_radio_1x : m_display_scale_radio_2x)->set_checked(true, GUI::AllowCallback::No);
m_monitor_widget->set_desktop_scale_factor(screen.scale_factor);
// Select the current selected resolution as it may differ
m_monitor_widget->set_desktop_resolution(current_resolution);
m_resolution_combo->set_selected_index(index, GUI::AllowCallback::No);
m_monitor_widget->update();
return {};
}
void MonitorSettingsWidget::apply_settings()
{
// Fetch the latest configuration again, in case it has been changed by someone else.
// This isn't technically race free, but if the user automates changing settings we can't help...
auto current_layout = GUI::ConnectionToWindowServer::the().get_screen_layout();
if (m_screen_layout != current_layout) {
auto result = GUI::ConnectionToWindowServer::the().set_screen_layout(m_screen_layout, false);
// Run load_current_settings to refresh screen info.
if (result.success() && !load_current_settings().is_error()) {
auto seconds_until_revert = 10;
auto box_text = [this, &seconds_until_revert]() -> ErrorOr<String> {
auto output = String::formatted("Do you want to keep the new settings? They will be reverted after {} {}.",
seconds_until_revert, seconds_until_revert == 1 ? "second" : "seconds");
if (output.is_error()) {
GUI::MessageBox::show_error(window(), "Unable to apply changes"sv);
return Error::from_string_literal("Unable to create a formatted string");
}
return output.release_value();
};
auto current_box_text_or_error = box_text();
if (current_box_text_or_error.is_error())
return;
auto current_box_text = current_box_text_or_error.release_value();
auto box = GUI::MessageBox::create(window(), current_box_text, "Apply new screen layout"sv,
GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo)
.release_value_but_fixme_should_propagate_errors();
box->set_icon(window()->icon());
// If after 10 seconds the user doesn't close the message box, just close it.
auto revert_timer_or_error = Core::Timer::create_repeating(1000, [&] {
seconds_until_revert -= 1;
current_box_text_or_error = box_text();
if (current_box_text_or_error.is_error()) {
seconds_until_revert = 0;
box->close();
return;
}
auto current_box_text = current_box_text_or_error.release_value();
box->set_text(current_box_text);
if (seconds_until_revert <= 0) {
box->close();
}
});
if (revert_timer_or_error.is_error()) {
GUI::MessageBox::show_error(window(), "Unable to apply changes"sv);
return;
}
auto revert_timer = revert_timer_or_error.release_value();
revert_timer->start();
// If the user selects "No", closes the window or the window gets closed by the 10 seconds timer, revert the changes.
if (box->exec() == GUI::MessageBox::ExecResult::Yes) {
auto save_result = GUI::ConnectionToWindowServer::the().save_screen_layout();
if (!save_result.success()) {
auto detailed_error_message = String::formatted("Error saving settings: {}", save_result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to save settings"sv);
}
} else {
auto restore_result = GUI::ConnectionToWindowServer::the().set_screen_layout(current_layout, false);
if (!restore_result.success() || load_current_settings().is_error()) {
auto detailed_error_message = String::formatted("Error restoring settings: {}", restore_result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to restore settings"sv);
}
}
} else {
auto detailed_error_message = String::formatted("Error setting screen layout: {}", result.error_msg());
if (!detailed_error_message.is_error())
GUI::MessageBox::show_error(window(), detailed_error_message.release_value());
else
GUI::MessageBox::show_error(window(), "Unable to set screen layout"sv);
}
}
}
void MonitorSettingsWidget::show_screen_numbers(bool show)
{
if (m_showing_screen_numbers == show)
return;
m_showing_screen_numbers = show;
GUI::ConnectionToWindowServer::the().async_show_screen_numbers(show);
}
void MonitorSettingsWidget::show_event(GUI::ShowEvent&)
{
show_screen_numbers(true);
}
void MonitorSettingsWidget::hide_event(GUI::HideEvent&)
{
show_screen_numbers(false);
}
}