ladybird/Userland/Applications/ThemeEditor/MainWidget.cpp
thankyouverycool d444724d24 ThemeEditor: Update GML and polish interface
Registers the PreviewWidget for addition directly into GML. Fixes
its previous double Frame borders. Also standardizes the Apply and
Reset buttons as DialogButtons and spaces them consistently with
other apps. Gives the TabWidget some tasteful container margins.
2022-11-19 11:04:11 +01:00

605 lines
27 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2022, networkException <networkexception@serenityos.org>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2021, Antonio Di Stefano <tonio9681@gmail.com>
* Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "MainWidget.h"
#include <Applications/ThemeEditor/AlignmentPropertyGML.h>
#include <Applications/ThemeEditor/ColorPropertyGML.h>
#include <Applications/ThemeEditor/FlagPropertyGML.h>
#include <Applications/ThemeEditor/MetricPropertyGML.h>
#include <Applications/ThemeEditor/PathPropertyGML.h>
#include <Applications/ThemeEditor/ThemeEditorGML.h>
#include <LibFileSystemAccessClient/Client.h>
#include <LibGUI/ActionGroup.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/ConnectionToWindowServer.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/Frame.h>
#include <LibGUI/GroupBox.h>
#include <LibGUI/Icon.h>
#include <LibGUI/ItemListModel.h>
#include <LibGUI/Label.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/ScrollableContainerWidget.h>
#include <LibGfx/Filters/ColorBlindnessFilter.h>
namespace ThemeEditor {
static const PropertyTab window_tab {
"Windows",
{
{ "General",
{ { Gfx::FlagRole::IsDark },
{ Gfx::AlignmentRole::TitleAlignment },
{ Gfx::MetricRole::TitleHeight },
{ Gfx::MetricRole::TitleButtonWidth },
{ Gfx::MetricRole::TitleButtonHeight },
{ Gfx::PathRole::TitleButtonIcons },
{ Gfx::FlagRole::TitleButtonsIconOnly } } },
{ "Border",
{ { Gfx::MetricRole::BorderThickness },
{ Gfx::MetricRole::BorderRadius } } },
{ "Active Window",
{ { Gfx::ColorRole::ActiveWindowBorder1 },
{ Gfx::ColorRole::ActiveWindowBorder2 },
{ Gfx::ColorRole::ActiveWindowTitle },
{ Gfx::ColorRole::ActiveWindowTitleShadow },
{ Gfx::ColorRole::ActiveWindowTitleStripes },
{ Gfx::PathRole::ActiveWindowShadow } } },
{ "Inactive Window",
{ { Gfx::ColorRole::InactiveWindowBorder1 },
{ Gfx::ColorRole::InactiveWindowBorder2 },
{ Gfx::ColorRole::InactiveWindowTitle },
{ Gfx::ColorRole::InactiveWindowTitleShadow },
{ Gfx::ColorRole::InactiveWindowTitleStripes },
{ Gfx::PathRole::InactiveWindowShadow } } },
{ "Highlighted Window",
{ { Gfx::ColorRole::HighlightWindowBorder1 },
{ Gfx::ColorRole::HighlightWindowBorder2 },
{ Gfx::ColorRole::HighlightWindowTitle },
{ Gfx::ColorRole::HighlightWindowTitleShadow },
{ Gfx::ColorRole::HighlightWindowTitleStripes } } },
{ "Moving Window",
{ { Gfx::ColorRole::MovingWindowBorder1 },
{ Gfx::ColorRole::MovingWindowBorder2 },
{ Gfx::ColorRole::MovingWindowTitle },
{ Gfx::ColorRole::MovingWindowTitleShadow },
{ Gfx::ColorRole::MovingWindowTitleStripes } } },
{ "Contents",
{ { Gfx::ColorRole::Window },
{ Gfx::ColorRole::WindowText } } },
{ "Desktop",
{ { Gfx::ColorRole::DesktopBackground },
{ Gfx::PathRole::TaskbarShadow } } },
}
};
static const PropertyTab widgets_tab {
"Widgets",
{
{ "General",
{ { Gfx::ColorRole::Accent },
{ Gfx::ColorRole::Base },
{ Gfx::ColorRole::ThreedHighlight },
{ Gfx::ColorRole::ThreedShadow1 },
{ Gfx::ColorRole::ThreedShadow2 },
{ Gfx::ColorRole::HoverHighlight } } },
{ "Text",
{ { Gfx::ColorRole::BaseText },
{ Gfx::ColorRole::DisabledTextFront },
{ Gfx::ColorRole::DisabledTextBack },
{ Gfx::ColorRole::PlaceholderText } } },
{ "Links",
{ { Gfx::ColorRole::Link },
{ Gfx::ColorRole::ActiveLink },
{ Gfx::ColorRole::VisitedLink } } },
{ "Buttons",
{ { Gfx::ColorRole::Button },
{ Gfx::ColorRole::ButtonText } } },
{ "Tooltips",
{ { Gfx::ColorRole::Tooltip },
{ Gfx::ColorRole::TooltipText },
{ Gfx::PathRole::TooltipShadow } } },
{ "Trays",
{ { Gfx::ColorRole::Tray },
{ Gfx::ColorRole::TrayText } } },
{ "Ruler",
{ { Gfx::ColorRole::Ruler },
{ Gfx::ColorRole::RulerBorder },
{ Gfx::ColorRole::RulerActiveText },
{ Gfx::ColorRole::RulerInactiveText } } },
{ "Gutter",
{ { Gfx::ColorRole::Gutter },
{ Gfx::ColorRole::GutterBorder } } },
{ "Rubber Band",
{ { Gfx::ColorRole::RubberBandBorder },
{ Gfx::ColorRole::RubberBandFill } } },
{ "Menus",
{ { Gfx::ColorRole::MenuBase },
{ Gfx::ColorRole::MenuBaseText },
{ Gfx::ColorRole::MenuSelection },
{ Gfx::ColorRole::MenuSelectionText },
{ Gfx::ColorRole::MenuStripe },
{ Gfx::PathRole::MenuShadow } } },
{ "Selection",
{ { Gfx::ColorRole::FocusOutline },
{ Gfx::ColorRole::TextCursor },
{ Gfx::ColorRole::Selection },
{ Gfx::ColorRole::SelectionText },
{ Gfx::ColorRole::InactiveSelection },
{ Gfx::ColorRole::InactiveSelectionText },
{ Gfx::ColorRole::HighlightSearching },
{ Gfx::ColorRole::HighlightSearchingText } } },
}
};
static const PropertyTab syntax_highlighting_tab {
"Syntax Highlighting",
{
{ "General",
{ { Gfx::ColorRole::SyntaxComment },
{ Gfx::ColorRole::SyntaxControlKeyword },
{ Gfx::ColorRole::SyntaxIdentifier },
{ Gfx::ColorRole::SyntaxKeyword },
{ Gfx::ColorRole::SyntaxNumber },
{ Gfx::ColorRole::SyntaxOperator },
{ Gfx::ColorRole::SyntaxPreprocessorStatement },
{ Gfx::ColorRole::SyntaxPreprocessorValue },
{ Gfx::ColorRole::SyntaxPunctuation },
{ Gfx::ColorRole::SyntaxString },
{ Gfx::ColorRole::SyntaxType },
{ Gfx::ColorRole::SyntaxFunction },
{ Gfx::ColorRole::SyntaxVariable },
{ Gfx::ColorRole::SyntaxCustomType },
{ Gfx::ColorRole::SyntaxNamespace },
{ Gfx::ColorRole::SyntaxMember },
{ Gfx::ColorRole::SyntaxParameter } } },
}
};
MainWidget::MainWidget()
: m_current_palette(GUI::Application::the()->palette())
{
load_from_gml(theme_editor_gml);
m_alignment_model = MUST(AlignmentModel::try_create());
m_preview_widget = find_descendant_of_type_named<ThemeEditor::PreviewWidget>("preview_widget");
m_property_tabs = find_descendant_of_type_named<GUI::TabWidget>("property_tabs");
add_property_tab(window_tab);
add_property_tab(widgets_tab);
add_property_tab(syntax_highlighting_tab);
build_override_controls();
}
ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
{
auto file_menu = TRY(window.try_add_menu("&File"));
TRY(file_menu->try_add_action(GUI::CommonActions::make_open_action([&](auto&) {
if (request_close() == GUI::Window::CloseRequestDecision::StayOpen)
return;
auto response = FileSystemAccessClient::Client::the().try_open_file(&window, "Select theme file", "/res/themes"sv);
if (response.is_error())
return;
load_from_file(*response.value());
})));
m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
if (m_path.has_value()) {
auto result = FileSystemAccessClient::Client::the().try_request_file(&window, *m_path, Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
if (result.is_error())
return;
save_to_file(result.value());
} else {
auto result = FileSystemAccessClient::Client::the().try_save_file(&window, "Theme", "ini", Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
if (result.is_error())
return;
save_to_file(result.value());
}
});
TRY(file_menu->try_add_action(*m_save_action));
TRY(file_menu->try_add_action(GUI::CommonActions::make_save_as_action([&](auto&) {
auto result = FileSystemAccessClient::Client::the().try_save_file(&window, "Theme", "ini", Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
if (result.is_error())
return;
save_to_file(result.value());
})));
TRY(file_menu->try_add_separator());
TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) {
if (request_close() == GUI::Window::CloseRequestDecision::Close)
GUI::Application::the()->quit();
})));
TRY(window.try_add_menu(TRY(GUI::CommonMenus::make_accessibility_menu(*m_preview_widget))));
auto help_menu = TRY(window.try_add_menu("&Help"));
TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(&window)));
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Theme Editor", GUI::Icon::default_icon("app-theme-editor"sv), &window)));
return {};
}
void MainWidget::update_title()
{
window()->set_title(String::formatted("{}[*] - Theme Editor", m_path.value_or("Untitled")));
}
GUI::Window::CloseRequestDecision MainWidget::request_close()
{
if (!window()->is_modified())
return GUI::Window::CloseRequestDecision::Close;
auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), m_path.value_or(""), m_last_modified_time);
if (result == GUI::MessageBox::ExecResult::Yes) {
m_save_action->activate();
if (window()->is_modified())
return GUI::Window::CloseRequestDecision::StayOpen;
return GUI::Window::CloseRequestDecision::Close;
}
if (result == GUI::MessageBox::ExecResult::No)
return GUI::Window::CloseRequestDecision::Close;
return GUI::Window::CloseRequestDecision::StayOpen;
}
void MainWidget::set_path(String path)
{
m_path = path;
update_title();
}
void MainWidget::save_to_file(Core::File& file)
{
auto theme = Core::ConfigFile::open(file.filename(), file.leak_fd()).release_value_but_fixme_should_propagate_errors();
#define __ENUMERATE_ALIGNMENT_ROLE(role) theme->write_entry("Alignments", to_string(Gfx::AlignmentRole::role), to_string(m_current_palette.alignment(Gfx::AlignmentRole::role)));
ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
#undef __ENUMERATE_ALIGNMENT_ROLE
#define __ENUMERATE_COLOR_ROLE(role) theme->write_entry("Colors", to_string(Gfx::ColorRole::role), m_current_palette.color(Gfx::ColorRole::role).to_string());
ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
#undef __ENUMERATE_COLOR_ROLE
#define __ENUMERATE_FLAG_ROLE(role) theme->write_bool_entry("Flags", to_string(Gfx::FlagRole::role), m_current_palette.flag(Gfx::FlagRole::role));
ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
#undef __ENUMERATE_FLAG_ROLE
#define __ENUMERATE_METRIC_ROLE(role) theme->write_num_entry("Metrics", to_string(Gfx::MetricRole::role), m_current_palette.metric(Gfx::MetricRole::role));
ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
#undef __ENUMERATE_METRIC_ROLE
#define __ENUMERATE_PATH_ROLE(role) theme->write_entry("Paths", to_string(Gfx::PathRole::role), m_current_palette.path(Gfx::PathRole::role));
ENUMERATE_PATH_ROLES(__ENUMERATE_PATH_ROLE)
#undef __ENUMERATE_PATH_ROLE
auto sync_result = theme->sync();
if (sync_result.is_error()) {
GUI::MessageBox::show_error(window(), String::formatted("Failed to save theme file: {}", sync_result.error()));
} else {
m_last_modified_time = Time::now_monotonic();
set_path(file.filename());
window()->set_modified(false);
}
}
ErrorOr<Core::AnonymousBuffer> MainWidget::encode()
{
auto buffer = TRY(Core::AnonymousBuffer::create_with_size(sizeof(Gfx::SystemTheme)));
auto* data = buffer.data<Gfx::SystemTheme>();
#define __ENUMERATE_ALIGNMENT_ROLE(role) \
data->alignment[(int)Gfx::AlignmentRole::role] = m_current_palette.alignment(Gfx::AlignmentRole::role);
ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
#undef __ENUMERATE_ALIGNMENT_ROLE
#define __ENUMERATE_COLOR_ROLE(role) \
data->color[(int)Gfx::ColorRole::role] = m_current_palette.color(Gfx::ColorRole::role).value();
ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
#undef __ENUMERATE_COLOR_ROLE
#define __ENUMERATE_FLAG_ROLE(role) \
data->flag[(int)Gfx::FlagRole::role] = m_current_palette.flag(Gfx::FlagRole::role);
ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
#undef __ENUMERATE_FLAG_ROLE
#define __ENUMERATE_METRIC_ROLE(role) \
data->metric[(int)Gfx::MetricRole::role] = m_current_palette.metric(Gfx::MetricRole::role);
ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
#undef __ENUMERATE_METRIC_ROLE
#define ENCODE_PATH(role, allow_empty) \
do { \
auto path = m_current_palette.path(Gfx::PathRole::role); \
char const* characters; \
if (path.is_empty()) { \
switch (Gfx::PathRole::role) { \
case Gfx::PathRole::TitleButtonIcons: \
characters = "/res/icons/16x16/"; \
break; \
default: \
characters = allow_empty ? "" : "/res/"; \
} \
} \
characters = path.characters(); \
memcpy(data->path[(int)Gfx::PathRole::role], characters, min(strlen(characters) + 1, sizeof(data->path[(int)Gfx::PathRole::role]))); \
data->path[(int)Gfx::PathRole::role][sizeof(data->path[(int)Gfx::PathRole::role]) - 1] = '\0'; \
} while (0)
ENCODE_PATH(TitleButtonIcons, false);
ENCODE_PATH(ActiveWindowShadow, true);
ENCODE_PATH(InactiveWindowShadow, true);
ENCODE_PATH(TaskbarShadow, true);
ENCODE_PATH(MenuShadow, true);
ENCODE_PATH(TooltipShadow, true);
return buffer;
}
void MainWidget::build_override_controls()
{
auto* theme_override_controls = find_descendant_of_type_named<GUI::Widget>("theme_override_controls");
m_theme_override_apply = theme_override_controls->find_child_of_type_named<GUI::DialogButton>("apply_button");
m_theme_override_reset = theme_override_controls->find_child_of_type_named<GUI::DialogButton>("reset_button");
m_theme_override_apply->on_click = [&](auto) {
auto encoded = encode();
if (encoded.is_error())
return;
GUI::ConnectionToWindowServer::the().async_set_system_theme_override(encoded.value());
};
m_theme_override_reset->on_click = [&](auto) {
GUI::ConnectionToWindowServer::the().async_clear_system_theme_override();
};
GUI::Application::the()->on_theme_change = [&]() {
auto override_active = GUI::ConnectionToWindowServer::the().is_system_theme_overridden();
m_theme_override_apply->set_enabled(!override_active && window()->is_modified());
m_theme_override_reset->set_enabled(override_active);
};
}
void MainWidget::add_property_tab(PropertyTab const& property_tab)
{
auto& scrollable_container = m_property_tabs->add_tab<GUI::ScrollableContainerWidget>(property_tab.title);
scrollable_container.set_should_hide_unnecessary_scrollbars(true);
auto properties_list = GUI::Widget::construct();
scrollable_container.set_widget(properties_list);
properties_list->set_layout<GUI::VerticalBoxLayout>();
properties_list->layout()->set_spacing(12);
properties_list->layout()->set_margins({ 8 });
for (auto const& group : property_tab.property_groups) {
NonnullRefPtr<GUI::GroupBox> group_box = properties_list->add<GUI::GroupBox>(group.title);
group_box->set_layout<GUI::VerticalBoxLayout>();
group_box->layout()->set_spacing(12);
// 1px less on the left makes the text line up with the group title.
group_box->layout()->set_margins({ 8, 8, 8, 7 });
group_box->set_preferred_height(GUI::SpecialDimension::Fit);
for (auto const& property : group.properties) {
NonnullRefPtr<GUI::Widget> row_widget = group_box->add<GUI::Widget>();
row_widget->set_fixed_height(22);
property.role.visit(
[&](Gfx::AlignmentRole role) {
row_widget->load_from_gml(alignment_property_gml);
auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
name_label.set_text(to_string(role));
auto& alignment_picker = *row_widget->find_descendant_of_type_named<GUI::ComboBox>("combo_box");
alignment_picker.set_model(*m_alignment_model);
alignment_picker.on_change = [&, role](auto&, auto& index) {
set_alignment(role, index.data(GUI::ModelRole::Custom).to_text_alignment(Gfx::TextAlignment::CenterLeft));
};
alignment_picker.set_selected_index(m_alignment_model->index_of(m_current_palette.alignment(role)), GUI::AllowCallback::No);
VERIFY(m_alignment_inputs[to_underlying(role)].is_null());
m_alignment_inputs[to_underlying(role)] = alignment_picker;
},
[&](Gfx::ColorRole role) {
row_widget->load_from_gml(color_property_gml);
auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
name_label.set_text(to_string(role));
auto& color_input = *row_widget->find_descendant_of_type_named<GUI::ColorInput>("color_input");
color_input.on_change = [&, role] {
set_color(role, color_input.color());
};
color_input.set_color(m_current_palette.color(role), GUI::AllowCallback::No);
VERIFY(m_color_inputs[to_underlying(role)].is_null());
m_color_inputs[to_underlying(role)] = color_input;
},
[&](Gfx::FlagRole role) {
row_widget->load_from_gml(flag_property_gml);
auto& checkbox = *row_widget->find_descendant_of_type_named<GUI::CheckBox>("checkbox");
checkbox.set_text(to_string(role));
checkbox.on_checked = [&, role](bool checked) {
set_flag(role, checked);
};
checkbox.set_checked(m_current_palette.flag(role), GUI::AllowCallback::No);
VERIFY(m_flag_inputs[to_underlying(role)].is_null());
m_flag_inputs[to_underlying(role)] = checkbox;
},
[&](Gfx::MetricRole role) {
row_widget->load_from_gml(metric_property_gml);
auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
name_label.set_text(to_string(role));
auto& spin_box = *row_widget->find_descendant_of_type_named<GUI::SpinBox>("spin_box");
spin_box.on_change = [&, role](int value) {
set_metric(role, value);
};
spin_box.set_value(m_current_palette.metric(role), GUI::AllowCallback::No);
VERIFY(m_metric_inputs[to_underlying(role)].is_null());
m_metric_inputs[to_underlying(role)] = spin_box;
},
[&](Gfx::PathRole role) {
row_widget->load_from_gml(path_property_gml);
auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
name_label.set_text(to_string(role));
auto& path_input = *row_widget->find_descendant_of_type_named<GUI::TextBox>("path_input");
path_input.on_change = [&, role] {
set_path(role, path_input.text());
};
path_input.set_text(m_current_palette.path(role), GUI::AllowCallback::No);
auto& path_picker_button = *row_widget->find_descendant_of_type_named<GUI::Button>("path_picker_button");
auto picker_target = (role == Gfx::PathRole::TitleButtonIcons) ? PathPickerTarget::Folder : PathPickerTarget::File;
path_picker_button.on_click = [&, role, picker_target](auto) {
show_path_picker_dialog(to_string(role), path_input, picker_target);
};
VERIFY(m_path_inputs[to_underlying(role)].is_null());
m_path_inputs[to_underlying(role)] = path_input;
});
}
}
}
void MainWidget::set_alignment(Gfx::AlignmentRole role, Gfx::TextAlignment value)
{
auto preview_palette = m_current_palette;
preview_palette.set_alignment(role, value);
set_palette(preview_palette);
}
void MainWidget::set_color(Gfx::ColorRole role, Gfx::Color value)
{
auto preview_palette = m_current_palette;
preview_palette.set_color(role, value);
set_palette(preview_palette);
}
void MainWidget::set_flag(Gfx::FlagRole role, bool value)
{
auto preview_palette = m_current_palette;
preview_palette.set_flag(role, value);
set_palette(preview_palette);
}
void MainWidget::set_metric(Gfx::MetricRole role, int value)
{
auto preview_palette = m_current_palette;
preview_palette.set_metric(role, value);
set_palette(preview_palette);
}
void MainWidget::set_path(Gfx::PathRole role, String value)
{
auto preview_palette = m_current_palette;
preview_palette.set_path(role, value);
set_palette(preview_palette);
}
void MainWidget::set_palette(Gfx::Palette palette)
{
m_current_palette = move(palette);
m_preview_widget->set_preview_palette(m_current_palette);
m_theme_override_apply->set_enabled(true);
window()->set_modified(true);
}
void MainWidget::show_path_picker_dialog(StringView property_display_name, GUI::TextBox& path_input, PathPickerTarget path_picker_target)
{
bool open_folder = path_picker_target == PathPickerTarget::Folder;
auto window_title = String::formatted(open_folder ? "Select {} folder"sv : "Select {} file"sv, property_display_name);
auto target_path = path_input.text();
if (Core::File::exists(target_path)) {
if (!Core::File::is_directory(target_path))
target_path = LexicalPath::dirname(target_path);
} else {
target_path = "/res/icons";
}
auto result = GUI::FilePicker::get_open_filepath(window(), window_title, target_path, open_folder);
if (!result.has_value())
return;
path_input.set_text(*result);
}
void MainWidget::load_from_file(Core::File& file)
{
auto config_file = Core::ConfigFile::open(file.filename(), file.leak_fd()).release_value_but_fixme_should_propagate_errors();
auto theme = Gfx::load_system_theme(config_file);
VERIFY(theme.is_valid());
auto new_palette = Gfx::Palette(Gfx::PaletteImpl::create_with_anonymous_buffer(theme));
set_palette(move(new_palette));
set_path(file.filename());
#define __ENUMERATE_ALIGNMENT_ROLE(role) \
if (auto alignment_input = m_alignment_inputs[to_underlying(Gfx::AlignmentRole::role)]) \
alignment_input->set_selected_index(m_alignment_model->index_of(m_current_palette.alignment(Gfx::AlignmentRole::role)), GUI::AllowCallback::No);
ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
#undef __ENUMERATE_ALIGNMENT_ROLE
#define __ENUMERATE_COLOR_ROLE(role) \
if (auto color_input = m_color_inputs[to_underlying(Gfx::ColorRole::role)]) \
color_input->set_color(m_current_palette.color(Gfx::ColorRole::role), GUI::AllowCallback::No);
ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
#undef __ENUMERATE_COLOR_ROLE
#define __ENUMERATE_FLAG_ROLE(role) \
if (auto flag_input = m_flag_inputs[to_underlying(Gfx::FlagRole::role)]) \
flag_input->set_checked(m_current_palette.flag(Gfx::FlagRole::role), GUI::AllowCallback::No);
ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
#undef __ENUMERATE_FLAG_ROLE
#define __ENUMERATE_METRIC_ROLE(role) \
if (auto metric_input = m_metric_inputs[to_underlying(Gfx::MetricRole::role)]) \
metric_input->set_value(m_current_palette.metric(Gfx::MetricRole::role), GUI::AllowCallback::No);
ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
#undef __ENUMERATE_METRIC_ROLE
#define __ENUMERATE_PATH_ROLE(role) \
if (auto path_input = m_path_inputs[to_underlying(Gfx::PathRole::role)]) \
path_input->set_text(m_current_palette.path(Gfx::PathRole::role), GUI::AllowCallback::No);
ENUMERATE_PATH_ROLES(__ENUMERATE_PATH_ROLE)
#undef __ENUMERATE_PATH_ROLE
m_last_modified_time = Time::now_monotonic();
window()->set_modified(false);
}
}