2022-05-10 16:54:43 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
|
|
|
* Copyright (c) 2021, Jakob-Niklas See <git@nwex.de>
|
|
|
|
* 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"
|
2022-05-11 14:56:16 +03:00
|
|
|
#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>
|
2022-05-10 16:54:43 +03:00
|
|
|
#include <Applications/ThemeEditor/ThemeEditorGML.h>
|
|
|
|
#include <LibFileSystemAccessClient/Client.h>
|
|
|
|
#include <LibGUI/ActionGroup.h>
|
|
|
|
#include <LibGUI/Application.h>
|
|
|
|
#include <LibGUI/BoxLayout.h>
|
|
|
|
#include <LibGUI/Button.h>
|
|
|
|
#include <LibGUI/FilePicker.h>
|
|
|
|
#include <LibGUI/Frame.h>
|
|
|
|
#include <LibGUI/Icon.h>
|
|
|
|
#include <LibGUI/ItemListModel.h>
|
2022-05-11 14:56:16 +03:00
|
|
|
#include <LibGUI/Label.h>
|
2022-05-10 16:54:43 +03:00
|
|
|
#include <LibGUI/Menu.h>
|
|
|
|
#include <LibGUI/Menubar.h>
|
|
|
|
#include <LibGUI/MessageBox.h>
|
2022-05-11 14:56:16 +03:00
|
|
|
#include <LibGUI/ScrollableContainerWidget.h>
|
2022-05-10 16:54:43 +03:00
|
|
|
#include <LibGfx/Filters/ColorBlindnessFilter.h>
|
|
|
|
|
|
|
|
namespace ThemeEditor {
|
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
static const PropertyTab window_tab {
|
|
|
|
"Windows",
|
2022-05-10 16:54:43 +03:00
|
|
|
{
|
2022-05-11 14:56:16 +03:00
|
|
|
{ Gfx::FlagRole::IsDark },
|
|
|
|
{ Gfx::AlignmentRole::TitleAlignment },
|
|
|
|
{ Gfx::MetricRole::TitleHeight },
|
|
|
|
{ Gfx::MetricRole::TitleButtonWidth },
|
|
|
|
{ Gfx::MetricRole::TitleButtonHeight },
|
|
|
|
{ Gfx::PathRole::TitleButtonIcons },
|
|
|
|
{ Gfx::FlagRole::TitleButtonsIconOnly },
|
|
|
|
|
|
|
|
{ Gfx::MetricRole::BorderThickness },
|
|
|
|
{ Gfx::MetricRole::BorderRadius },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::ActiveWindowBorder1 },
|
|
|
|
{ Gfx::ColorRole::ActiveWindowBorder2 },
|
|
|
|
{ Gfx::ColorRole::ActiveWindowTitle },
|
|
|
|
{ Gfx::ColorRole::ActiveWindowTitleShadow },
|
|
|
|
{ Gfx::ColorRole::ActiveWindowTitleStripes },
|
|
|
|
{ Gfx::PathRole::ActiveWindowShadow },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::InactiveWindowBorder1 },
|
|
|
|
{ Gfx::ColorRole::InactiveWindowBorder2 },
|
|
|
|
{ Gfx::ColorRole::InactiveWindowTitle },
|
|
|
|
{ Gfx::ColorRole::InactiveWindowTitleShadow },
|
|
|
|
{ Gfx::ColorRole::InactiveWindowTitleStripes },
|
|
|
|
{ Gfx::PathRole::InactiveWindowShadow },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::HighlightWindowBorder1 },
|
|
|
|
{ Gfx::ColorRole::HighlightWindowBorder2 },
|
|
|
|
{ Gfx::ColorRole::HighlightWindowTitle },
|
|
|
|
{ Gfx::ColorRole::HighlightWindowTitleShadow },
|
|
|
|
{ Gfx::ColorRole::HighlightWindowTitleStripes },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::MovingWindowBorder1 },
|
|
|
|
{ Gfx::ColorRole::MovingWindowBorder2 },
|
|
|
|
{ Gfx::ColorRole::MovingWindowTitle },
|
|
|
|
{ Gfx::ColorRole::MovingWindowTitleShadow },
|
|
|
|
{ Gfx::ColorRole::MovingWindowTitleStripes },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Window },
|
|
|
|
{ Gfx::ColorRole::WindowText },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::DesktopBackground },
|
|
|
|
{ Gfx::PathRole::TaskbarShadow },
|
2022-05-10 16:54:43 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
static const PropertyTab widgets_tab {
|
|
|
|
"Widgets",
|
2022-05-10 16:54:43 +03:00
|
|
|
{
|
2022-05-11 14:56:16 +03:00
|
|
|
{ Gfx::ColorRole::Accent },
|
|
|
|
{ Gfx::ColorRole::Base },
|
|
|
|
{ Gfx::ColorRole::ThreedHighlight },
|
|
|
|
{ Gfx::ColorRole::ThreedShadow1 },
|
|
|
|
{ Gfx::ColorRole::ThreedShadow2 },
|
|
|
|
{ Gfx::ColorRole::HoverHighlight },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::BaseText },
|
|
|
|
{ Gfx::ColorRole::DisabledTextFront },
|
|
|
|
{ Gfx::ColorRole::DisabledTextBack },
|
|
|
|
{ Gfx::ColorRole::PlaceholderText },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Link },
|
|
|
|
{ Gfx::ColorRole::ActiveLink },
|
|
|
|
{ Gfx::ColorRole::VisitedLink },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Button },
|
|
|
|
{ Gfx::ColorRole::ButtonText },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Tooltip },
|
|
|
|
{ Gfx::ColorRole::TooltipText },
|
|
|
|
{ Gfx::PathRole::TooltipShadow },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Tray },
|
|
|
|
{ Gfx::ColorRole::TrayText },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Ruler },
|
|
|
|
{ Gfx::ColorRole::RulerBorder },
|
|
|
|
{ Gfx::ColorRole::RulerActiveText },
|
|
|
|
{ Gfx::ColorRole::RulerInactiveText },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::Gutter },
|
|
|
|
{ Gfx::ColorRole::GutterBorder },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::RubberBandBorder },
|
|
|
|
{ Gfx::ColorRole::RubberBandFill },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::MenuBase },
|
|
|
|
{ Gfx::ColorRole::MenuBaseText },
|
|
|
|
{ Gfx::ColorRole::MenuSelection },
|
|
|
|
{ Gfx::ColorRole::MenuSelectionText },
|
|
|
|
{ Gfx::ColorRole::MenuStripe },
|
|
|
|
{ Gfx::PathRole::MenuShadow },
|
|
|
|
|
|
|
|
{ Gfx::ColorRole::FocusOutline },
|
|
|
|
{ Gfx::ColorRole::TextCursor },
|
|
|
|
{ Gfx::ColorRole::Selection },
|
|
|
|
{ Gfx::ColorRole::SelectionText },
|
|
|
|
{ Gfx::ColorRole::InactiveSelection },
|
|
|
|
{ Gfx::ColorRole::InactiveSelectionText },
|
|
|
|
{ Gfx::ColorRole::HighlightSearching },
|
|
|
|
{ Gfx::ColorRole::HighlightSearchingText },
|
2022-05-10 16:54:43 +03:00
|
|
|
}
|
2022-05-11 14:56:16 +03:00
|
|
|
};
|
2022-05-10 16:54:43 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
static const PropertyTab syntax_highlighting_tab {
|
|
|
|
"Syntax Highlighting",
|
2022-05-10 16:54:43 +03:00
|
|
|
{
|
2022-05-11 14:56:16 +03:00
|
|
|
{ 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 },
|
2022-05-10 16:54:43 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
MainWidget::MainWidget(Optional<String> path, Gfx::Palette startup_preview_palette)
|
|
|
|
: m_path(path)
|
|
|
|
{
|
|
|
|
load_from_gml(theme_editor_gml);
|
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
m_alignment_model = MUST(AlignmentModel::try_create());
|
2022-05-10 16:54:43 +03:00
|
|
|
|
|
|
|
m_preview_widget = find_descendant_of_type_named<GUI::Frame>("preview_frame")
|
|
|
|
->add<ThemeEditor::PreviewWidget>(startup_preview_palette);
|
2022-05-11 14:56:16 +03:00
|
|
|
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);
|
2022-05-10 16:54:43 +03:00
|
|
|
|
|
|
|
m_preview_widget->on_palette_change = [&] {
|
|
|
|
window()->set_modified(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
m_preview_widget->on_theme_load_from_file = [&](String const& new_path) {
|
2022-05-11 14:56:16 +03:00
|
|
|
load_from_file(new_path);
|
2022-05-10 16:54:43 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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&) {
|
|
|
|
auto response = FileSystemAccessClient::Client::the().try_open_file(&window, "Select theme file", "/res/themes");
|
|
|
|
if (response.is_error())
|
|
|
|
return;
|
|
|
|
m_preview_widget->set_theme_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&) { GUI::Application::the()->quit(); })));
|
|
|
|
|
|
|
|
auto accessibility_menu = TRY(window.try_add_menu("&Accessibility"));
|
|
|
|
|
|
|
|
auto default_accessibility_action = GUI::Action::create_checkable("Default - non-impaired", { Mod_AltGr, Key_1 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(nullptr);
|
|
|
|
});
|
|
|
|
default_accessibility_action->set_checked(true);
|
|
|
|
|
|
|
|
auto pratanopia_accessibility_action = GUI::Action::create_checkable("Protanopia", { Mod_AltGr, Key_2 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_protanopia());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto pratanomaly_accessibility_action = GUI::Action::create_checkable("Protanomaly", { Mod_AltGr, Key_3 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_protanomaly());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto tritanopia_accessibility_action = GUI::Action::create_checkable("Tritanopia", { Mod_AltGr, Key_4 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_tritanopia());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto tritanomaly_accessibility_action = GUI::Action::create_checkable("Tritanomaly", { Mod_AltGr, Key_5 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_tritanomaly());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto deuteranopia_accessibility_action = GUI::Action::create_checkable("Deuteranopia", { Mod_AltGr, Key_6 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_deuteranopia());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto deuteranomaly_accessibility_action = GUI::Action::create_checkable("Deuteranomaly", { Mod_AltGr, Key_7 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_deuteranomaly());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto achromatopsia_accessibility_action = GUI::Action::create_checkable("Achromatopsia", { Mod_AltGr, Key_8 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_achromatopsia());
|
|
|
|
});
|
|
|
|
|
|
|
|
auto achromatomaly_accessibility_action = GUI::Action::create_checkable("Achromatomaly", { Mod_AltGr, Key_9 }, [&](auto&) {
|
|
|
|
m_preview_widget->set_color_filter(Gfx::ColorBlindnessFilter::create_achromatomaly());
|
|
|
|
});
|
|
|
|
|
|
|
|
m_preview_type_action_group = make<GUI::ActionGroup>();
|
|
|
|
m_preview_type_action_group->set_exclusive(true);
|
|
|
|
m_preview_type_action_group->add_action(*default_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*pratanopia_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*pratanomaly_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*tritanopia_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*tritanomaly_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*deuteranopia_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*deuteranomaly_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*achromatopsia_accessibility_action);
|
|
|
|
m_preview_type_action_group->add_action(*achromatomaly_accessibility_action);
|
|
|
|
|
|
|
|
TRY(accessibility_menu->try_add_action(default_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(pratanopia_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(pratanomaly_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(tritanopia_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(tritanomaly_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(deuteranopia_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(deuteranomaly_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(achromatopsia_accessibility_action));
|
|
|
|
TRY(accessibility_menu->try_add_action(achromatomaly_accessibility_action));
|
|
|
|
|
|
|
|
auto help_menu = TRY(window.try_add_menu("&Help"));
|
|
|
|
TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Theme Editor", GUI::Icon::default_icon("app-theme-editor"), &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();
|
2022-05-05 19:48:24 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
#define __ENUMERATE_ALIGNMENT_ROLE(role) theme->write_entry("Alignments", to_string(Gfx::AlignmentRole::role), to_string(m_preview_widget->preview_palette().alignment(Gfx::AlignmentRole::role)));
|
|
|
|
ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
|
|
|
|
#undef __ENUMERATE_ALIGNMENT_ROLE
|
2022-05-05 19:48:24 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
#define __ENUMERATE_COLOR_ROLE(role) theme->write_entry("Colors", to_string(Gfx::ColorRole::role), m_preview_widget->preview_palette().color(Gfx::ColorRole::role).to_string());
|
|
|
|
ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
|
|
|
|
#undef __ENUMERATE_COLOR_ROLE
|
2022-05-10 16:54:43 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
#define __ENUMERATE_FLAG_ROLE(role) theme->write_bool_entry("Flags", to_string(Gfx::FlagRole::role), m_preview_widget->preview_palette().flag(Gfx::FlagRole::role));
|
|
|
|
ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
|
|
|
|
#undef __ENUMERATE_FLAG_ROLE
|
2022-05-10 16:54:43 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
#define __ENUMERATE_METRIC_ROLE(role) theme->write_num_entry("Metrics", to_string(Gfx::MetricRole::role), m_preview_widget->preview_palette().metric(Gfx::MetricRole::role));
|
|
|
|
ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
|
|
|
|
#undef __ENUMERATE_METRIC_ROLE
|
2022-05-10 16:54:43 +03:00
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
#define __ENUMERATE_PATH_ROLE(role) theme->write_entry("Paths", to_string(Gfx::PathRole::role), m_preview_widget->preview_palette().path(Gfx::PathRole::role));
|
|
|
|
ENUMERATE_PATH_ROLES(__ENUMERATE_PATH_ROLE)
|
|
|
|
#undef __ENUMERATE_PATH_ROLE
|
2022-05-10 16:54:43 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-11 14:56:16 +03:00
|
|
|
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& property : property_tab.properties) {
|
|
|
|
NonnullRefPtr<GUI::Widget> row_widget = properties_list->add<GUI::Widget>();
|
|
|
|
row_widget->set_fixed_height(24);
|
|
|
|
|
|
|
|
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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_palette();
|
|
|
|
preview_palette.set_alignment(role, value);
|
|
|
|
m_preview_widget->set_preview_palette(preview_palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWidget::set_color(Gfx::ColorRole role, Gfx::Color value)
|
|
|
|
{
|
|
|
|
auto preview_palette = m_preview_widget->preview_palette();
|
|
|
|
preview_palette.set_color(role, value);
|
|
|
|
m_preview_widget->set_preview_palette(preview_palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWidget::set_flag(Gfx::FlagRole role, bool value)
|
|
|
|
{
|
|
|
|
auto preview_palette = m_preview_widget->preview_palette();
|
|
|
|
preview_palette.set_flag(role, value);
|
|
|
|
m_preview_widget->set_preview_palette(preview_palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWidget::set_metric(Gfx::MetricRole role, int value)
|
|
|
|
{
|
|
|
|
auto preview_palette = m_preview_widget->preview_palette();
|
|
|
|
preview_palette.set_metric(role, value);
|
|
|
|
m_preview_widget->set_preview_palette(preview_palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWidget::set_path(Gfx::PathRole role, String value)
|
|
|
|
{
|
|
|
|
auto preview_palette = m_preview_widget->preview_palette();
|
|
|
|
preview_palette.set_path(role, value);
|
|
|
|
m_preview_widget->set_preview_palette(preview_palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
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" : "Select {} file", 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(String const& new_path)
|
|
|
|
{
|
|
|
|
set_path(new_path);
|
|
|
|
|
|
|
|
#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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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_preview_widget->preview_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);
|
|
|
|
}
|
|
|
|
|
2022-05-10 16:54:43 +03:00
|
|
|
}
|