MainWidget.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021-2022, networkException <networkexception@serenityos.org>
  4. * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
  5. * Copyright (c) 2021, Antonio Di Stefano <tonio9681@gmail.com>
  6. * Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
  7. *
  8. * SPDX-License-Identifier: BSD-2-Clause
  9. */
  10. #include "MainWidget.h"
  11. #include <Applications/ThemeEditor/AlignmentPropertyGML.h>
  12. #include <Applications/ThemeEditor/ColorPropertyGML.h>
  13. #include <Applications/ThemeEditor/FlagPropertyGML.h>
  14. #include <Applications/ThemeEditor/MetricPropertyGML.h>
  15. #include <Applications/ThemeEditor/PathPropertyGML.h>
  16. #include <Applications/ThemeEditor/ThemeEditorGML.h>
  17. #include <LibFileSystemAccessClient/Client.h>
  18. #include <LibGUI/ActionGroup.h>
  19. #include <LibGUI/BoxLayout.h>
  20. #include <LibGUI/Button.h>
  21. #include <LibGUI/ConnectionToWindowServer.h>
  22. #include <LibGUI/FilePicker.h>
  23. #include <LibGUI/Frame.h>
  24. #include <LibGUI/GroupBox.h>
  25. #include <LibGUI/Icon.h>
  26. #include <LibGUI/ItemListModel.h>
  27. #include <LibGUI/Label.h>
  28. #include <LibGUI/Menu.h>
  29. #include <LibGUI/Menubar.h>
  30. #include <LibGUI/MessageBox.h>
  31. #include <LibGUI/ScrollableContainerWidget.h>
  32. #include <LibGfx/Filters/ColorBlindnessFilter.h>
  33. namespace ThemeEditor {
  34. static const PropertyTab window_tab {
  35. "Windows",
  36. {
  37. { "General",
  38. { { Gfx::FlagRole::IsDark },
  39. { Gfx::AlignmentRole::TitleAlignment },
  40. { Gfx::MetricRole::TitleHeight },
  41. { Gfx::MetricRole::TitleButtonWidth },
  42. { Gfx::MetricRole::TitleButtonHeight },
  43. { Gfx::PathRole::TitleButtonIcons },
  44. { Gfx::FlagRole::TitleButtonsIconOnly } } },
  45. { "Border",
  46. { { Gfx::MetricRole::BorderThickness },
  47. { Gfx::MetricRole::BorderRadius } } },
  48. { "Active Window",
  49. { { Gfx::ColorRole::ActiveWindowBorder1 },
  50. { Gfx::ColorRole::ActiveWindowBorder2 },
  51. { Gfx::ColorRole::ActiveWindowTitle },
  52. { Gfx::ColorRole::ActiveWindowTitleShadow },
  53. { Gfx::ColorRole::ActiveWindowTitleStripes },
  54. { Gfx::PathRole::ActiveWindowShadow } } },
  55. { "Inactive Window",
  56. { { Gfx::ColorRole::InactiveWindowBorder1 },
  57. { Gfx::ColorRole::InactiveWindowBorder2 },
  58. { Gfx::ColorRole::InactiveWindowTitle },
  59. { Gfx::ColorRole::InactiveWindowTitleShadow },
  60. { Gfx::ColorRole::InactiveWindowTitleStripes },
  61. { Gfx::PathRole::InactiveWindowShadow } } },
  62. { "Highlighted Window",
  63. { { Gfx::ColorRole::HighlightWindowBorder1 },
  64. { Gfx::ColorRole::HighlightWindowBorder2 },
  65. { Gfx::ColorRole::HighlightWindowTitle },
  66. { Gfx::ColorRole::HighlightWindowTitleShadow },
  67. { Gfx::ColorRole::HighlightWindowTitleStripes } } },
  68. { "Moving Window",
  69. { { Gfx::ColorRole::MovingWindowBorder1 },
  70. { Gfx::ColorRole::MovingWindowBorder2 },
  71. { Gfx::ColorRole::MovingWindowTitle },
  72. { Gfx::ColorRole::MovingWindowTitleShadow },
  73. { Gfx::ColorRole::MovingWindowTitleStripes } } },
  74. { "Contents",
  75. { { Gfx::ColorRole::Window },
  76. { Gfx::ColorRole::WindowText } } },
  77. { "Desktop",
  78. { { Gfx::ColorRole::DesktopBackground },
  79. { Gfx::PathRole::TaskbarShadow } } },
  80. }
  81. };
  82. static const PropertyTab widgets_tab {
  83. "Widgets",
  84. {
  85. { "General",
  86. { { Gfx::ColorRole::Accent },
  87. { Gfx::ColorRole::Base },
  88. { Gfx::ColorRole::ThreedHighlight },
  89. { Gfx::ColorRole::ThreedShadow1 },
  90. { Gfx::ColorRole::ThreedShadow2 },
  91. { Gfx::ColorRole::HoverHighlight } } },
  92. { "Text",
  93. { { Gfx::ColorRole::BaseText },
  94. { Gfx::ColorRole::DisabledTextFront },
  95. { Gfx::ColorRole::DisabledTextBack },
  96. { Gfx::ColorRole::PlaceholderText } } },
  97. { "Links",
  98. { { Gfx::ColorRole::Link },
  99. { Gfx::ColorRole::ActiveLink },
  100. { Gfx::ColorRole::VisitedLink } } },
  101. { "Buttons",
  102. { { Gfx::ColorRole::Button },
  103. { Gfx::ColorRole::ButtonText } } },
  104. { "Tooltips",
  105. { { Gfx::ColorRole::Tooltip },
  106. { Gfx::ColorRole::TooltipText },
  107. { Gfx::PathRole::TooltipShadow } } },
  108. { "Trays",
  109. { { Gfx::ColorRole::Tray },
  110. { Gfx::ColorRole::TrayText } } },
  111. { "Ruler",
  112. { { Gfx::ColorRole::Ruler },
  113. { Gfx::ColorRole::RulerBorder },
  114. { Gfx::ColorRole::RulerActiveText },
  115. { Gfx::ColorRole::RulerInactiveText } } },
  116. { "Gutter",
  117. { { Gfx::ColorRole::Gutter },
  118. { Gfx::ColorRole::GutterBorder } } },
  119. { "Rubber Band",
  120. { { Gfx::ColorRole::RubberBandBorder },
  121. { Gfx::ColorRole::RubberBandFill } } },
  122. { "Menus",
  123. { { Gfx::ColorRole::MenuBase },
  124. { Gfx::ColorRole::MenuBaseText },
  125. { Gfx::ColorRole::MenuSelection },
  126. { Gfx::ColorRole::MenuSelectionText },
  127. { Gfx::ColorRole::MenuStripe },
  128. { Gfx::PathRole::MenuShadow } } },
  129. { "Selection",
  130. { { Gfx::ColorRole::FocusOutline },
  131. { Gfx::ColorRole::TextCursor },
  132. { Gfx::ColorRole::Selection },
  133. { Gfx::ColorRole::SelectionText },
  134. { Gfx::ColorRole::InactiveSelection },
  135. { Gfx::ColorRole::InactiveSelectionText },
  136. { Gfx::ColorRole::HighlightSearching },
  137. { Gfx::ColorRole::HighlightSearchingText } } },
  138. }
  139. };
  140. static const PropertyTab syntax_highlighting_tab {
  141. "Syntax Highlighting",
  142. {
  143. { "General",
  144. { { Gfx::ColorRole::SyntaxComment },
  145. { Gfx::ColorRole::SyntaxControlKeyword },
  146. { Gfx::ColorRole::SyntaxIdentifier },
  147. { Gfx::ColorRole::SyntaxKeyword },
  148. { Gfx::ColorRole::SyntaxNumber },
  149. { Gfx::ColorRole::SyntaxOperator },
  150. { Gfx::ColorRole::SyntaxPreprocessorStatement },
  151. { Gfx::ColorRole::SyntaxPreprocessorValue },
  152. { Gfx::ColorRole::SyntaxPunctuation },
  153. { Gfx::ColorRole::SyntaxString },
  154. { Gfx::ColorRole::SyntaxType },
  155. { Gfx::ColorRole::SyntaxFunction },
  156. { Gfx::ColorRole::SyntaxVariable },
  157. { Gfx::ColorRole::SyntaxCustomType },
  158. { Gfx::ColorRole::SyntaxNamespace },
  159. { Gfx::ColorRole::SyntaxMember },
  160. { Gfx::ColorRole::SyntaxParameter } } },
  161. }
  162. };
  163. MainWidget::MainWidget()
  164. : m_current_palette(GUI::Application::the()->palette())
  165. {
  166. load_from_gml(theme_editor_gml);
  167. m_alignment_model = MUST(AlignmentModel::try_create());
  168. m_preview_widget = find_descendant_of_type_named<ThemeEditor::PreviewWidget>("preview_widget");
  169. m_property_tabs = find_descendant_of_type_named<GUI::TabWidget>("property_tabs");
  170. add_property_tab(window_tab);
  171. add_property_tab(widgets_tab);
  172. add_property_tab(syntax_highlighting_tab);
  173. build_override_controls();
  174. }
  175. ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
  176. {
  177. auto file_menu = TRY(window.try_add_menu("&File"));
  178. TRY(file_menu->try_add_action(GUI::CommonActions::make_open_action([&](auto&) {
  179. if (request_close() == GUI::Window::CloseRequestDecision::StayOpen)
  180. return;
  181. auto response = FileSystemAccessClient::Client::the().try_open_file(&window, "Select theme file", "/res/themes"sv);
  182. if (response.is_error())
  183. return;
  184. auto load_from_file_result = load_from_file(*response.value());
  185. if (load_from_file_result.is_error()) {
  186. GUI::MessageBox::show_error(&window, DeprecatedString::formatted("Can't open file named {}: {}", response.value()->filename(), load_from_file_result.error()));
  187. return;
  188. }
  189. })));
  190. m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
  191. if (m_path.has_value()) {
  192. auto result = FileSystemAccessClient::Client::the().try_request_file(&window, *m_path, Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
  193. if (result.is_error())
  194. return;
  195. save_to_file(result.value());
  196. } else {
  197. auto result = FileSystemAccessClient::Client::the().try_save_file(&window, "Theme", "ini", Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
  198. if (result.is_error())
  199. return;
  200. save_to_file(result.value());
  201. }
  202. });
  203. TRY(file_menu->try_add_action(*m_save_action));
  204. TRY(file_menu->try_add_action(GUI::CommonActions::make_save_as_action([&](auto&) {
  205. auto result = FileSystemAccessClient::Client::the().try_save_file(&window, "Theme", "ini", Core::OpenMode::ReadWrite | Core::OpenMode::Truncate);
  206. if (result.is_error())
  207. return;
  208. save_to_file(result.value());
  209. })));
  210. TRY(file_menu->try_add_separator());
  211. TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) {
  212. if (request_close() == GUI::Window::CloseRequestDecision::Close)
  213. GUI::Application::the()->quit();
  214. })));
  215. TRY(window.try_add_menu(TRY(GUI::CommonMenus::make_accessibility_menu(*m_preview_widget))));
  216. auto help_menu = TRY(window.try_add_menu("&Help"));
  217. TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(&window)));
  218. TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Theme Editor", GUI::Icon::default_icon("app-theme-editor"sv), &window)));
  219. return {};
  220. }
  221. void MainWidget::update_title()
  222. {
  223. window()->set_title(DeprecatedString::formatted("{}[*] - Theme Editor", m_path.value_or("Untitled")));
  224. }
  225. GUI::Window::CloseRequestDecision MainWidget::request_close()
  226. {
  227. if (!window()->is_modified())
  228. return GUI::Window::CloseRequestDecision::Close;
  229. auto result = GUI::MessageBox::ask_about_unsaved_changes(window(), m_path.value_or(""), m_last_modified_time);
  230. if (result == GUI::MessageBox::ExecResult::Yes) {
  231. m_save_action->activate();
  232. if (window()->is_modified())
  233. return GUI::Window::CloseRequestDecision::StayOpen;
  234. return GUI::Window::CloseRequestDecision::Close;
  235. }
  236. if (result == GUI::MessageBox::ExecResult::No)
  237. return GUI::Window::CloseRequestDecision::Close;
  238. return GUI::Window::CloseRequestDecision::StayOpen;
  239. }
  240. void MainWidget::set_path(DeprecatedString path)
  241. {
  242. m_path = path;
  243. update_title();
  244. }
  245. void MainWidget::save_to_file(Core::File& file)
  246. {
  247. auto theme = Core::ConfigFile::open(file.filename(), file.leak_fd()).release_value_but_fixme_should_propagate_errors();
  248. #define __ENUMERATE_ALIGNMENT_ROLE(role) theme->write_entry("Alignments", to_string(Gfx::AlignmentRole::role), to_string(m_current_palette.alignment(Gfx::AlignmentRole::role)));
  249. ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
  250. #undef __ENUMERATE_ALIGNMENT_ROLE
  251. #define __ENUMERATE_COLOR_ROLE(role) theme->write_entry("Colors", to_string(Gfx::ColorRole::role), m_current_palette.color(Gfx::ColorRole::role).to_deprecated_string());
  252. ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
  253. #undef __ENUMERATE_COLOR_ROLE
  254. #define __ENUMERATE_FLAG_ROLE(role) theme->write_bool_entry("Flags", to_string(Gfx::FlagRole::role), m_current_palette.flag(Gfx::FlagRole::role));
  255. ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
  256. #undef __ENUMERATE_FLAG_ROLE
  257. #define __ENUMERATE_METRIC_ROLE(role) theme->write_num_entry("Metrics", to_string(Gfx::MetricRole::role), m_current_palette.metric(Gfx::MetricRole::role));
  258. ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
  259. #undef __ENUMERATE_METRIC_ROLE
  260. #define __ENUMERATE_PATH_ROLE(role) theme->write_entry("Paths", to_string(Gfx::PathRole::role), m_current_palette.path(Gfx::PathRole::role));
  261. ENUMERATE_PATH_ROLES(__ENUMERATE_PATH_ROLE)
  262. #undef __ENUMERATE_PATH_ROLE
  263. auto sync_result = theme->sync();
  264. if (sync_result.is_error()) {
  265. GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Failed to save theme file: {}", sync_result.error()));
  266. } else {
  267. m_last_modified_time = Time::now_monotonic();
  268. set_path(file.filename());
  269. window()->set_modified(false);
  270. }
  271. }
  272. ErrorOr<Core::AnonymousBuffer> MainWidget::encode()
  273. {
  274. auto buffer = TRY(Core::AnonymousBuffer::create_with_size(sizeof(Gfx::SystemTheme)));
  275. auto* data = buffer.data<Gfx::SystemTheme>();
  276. #define __ENUMERATE_ALIGNMENT_ROLE(role) \
  277. data->alignment[(int)Gfx::AlignmentRole::role] = m_current_palette.alignment(Gfx::AlignmentRole::role);
  278. ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
  279. #undef __ENUMERATE_ALIGNMENT_ROLE
  280. #define __ENUMERATE_COLOR_ROLE(role) \
  281. data->color[(int)Gfx::ColorRole::role] = m_current_palette.color(Gfx::ColorRole::role).value();
  282. ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
  283. #undef __ENUMERATE_COLOR_ROLE
  284. #define __ENUMERATE_FLAG_ROLE(role) \
  285. data->flag[(int)Gfx::FlagRole::role] = m_current_palette.flag(Gfx::FlagRole::role);
  286. ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
  287. #undef __ENUMERATE_FLAG_ROLE
  288. #define __ENUMERATE_METRIC_ROLE(role) \
  289. data->metric[(int)Gfx::MetricRole::role] = m_current_palette.metric(Gfx::MetricRole::role);
  290. ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
  291. #undef __ENUMERATE_METRIC_ROLE
  292. #define ENCODE_PATH(role, allow_empty) \
  293. do { \
  294. auto path = m_current_palette.path(Gfx::PathRole::role); \
  295. char const* characters; \
  296. if (path.is_empty()) { \
  297. switch (Gfx::PathRole::role) { \
  298. case Gfx::PathRole::TitleButtonIcons: \
  299. characters = "/res/icons/16x16/"; \
  300. break; \
  301. default: \
  302. characters = allow_empty ? "" : "/res/"; \
  303. } \
  304. } \
  305. characters = path.characters(); \
  306. memcpy(data->path[(int)Gfx::PathRole::role], characters, min(strlen(characters) + 1, sizeof(data->path[(int)Gfx::PathRole::role]))); \
  307. data->path[(int)Gfx::PathRole::role][sizeof(data->path[(int)Gfx::PathRole::role]) - 1] = '\0'; \
  308. } while (0)
  309. ENCODE_PATH(TitleButtonIcons, false);
  310. ENCODE_PATH(ActiveWindowShadow, true);
  311. ENCODE_PATH(InactiveWindowShadow, true);
  312. ENCODE_PATH(TaskbarShadow, true);
  313. ENCODE_PATH(MenuShadow, true);
  314. ENCODE_PATH(TooltipShadow, true);
  315. return buffer;
  316. }
  317. void MainWidget::build_override_controls()
  318. {
  319. auto* theme_override_controls = find_descendant_of_type_named<GUI::Widget>("theme_override_controls");
  320. m_theme_override_apply = theme_override_controls->find_child_of_type_named<GUI::DialogButton>("apply_button");
  321. m_theme_override_reset = theme_override_controls->find_child_of_type_named<GUI::DialogButton>("reset_button");
  322. m_theme_override_apply->on_click = [&](auto) {
  323. auto encoded = encode();
  324. if (encoded.is_error())
  325. return;
  326. GUI::ConnectionToWindowServer::the().async_set_system_theme_override(encoded.value());
  327. };
  328. m_theme_override_reset->on_click = [&](auto) {
  329. GUI::ConnectionToWindowServer::the().async_clear_system_theme_override();
  330. };
  331. GUI::Application::the()->on_theme_change = [&]() {
  332. auto override_active = GUI::ConnectionToWindowServer::the().is_system_theme_overridden();
  333. m_theme_override_apply->set_enabled(!override_active && window()->is_modified());
  334. m_theme_override_reset->set_enabled(override_active);
  335. };
  336. }
  337. void MainWidget::add_property_tab(PropertyTab const& property_tab)
  338. {
  339. auto& scrollable_container = m_property_tabs->add_tab<GUI::ScrollableContainerWidget>(property_tab.title);
  340. scrollable_container.set_should_hide_unnecessary_scrollbars(true);
  341. auto properties_list = GUI::Widget::construct();
  342. scrollable_container.set_widget(properties_list);
  343. properties_list->set_layout<GUI::VerticalBoxLayout>();
  344. properties_list->layout()->set_spacing(12);
  345. properties_list->layout()->set_margins({ 8 });
  346. for (auto const& group : property_tab.property_groups) {
  347. NonnullRefPtr<GUI::GroupBox> group_box = properties_list->add<GUI::GroupBox>(group.title);
  348. group_box->set_layout<GUI::VerticalBoxLayout>();
  349. group_box->layout()->set_spacing(12);
  350. // 1px less on the left makes the text line up with the group title.
  351. group_box->layout()->set_margins({ 8, 8, 8, 7 });
  352. group_box->set_preferred_height(GUI::SpecialDimension::Fit);
  353. for (auto const& property : group.properties) {
  354. NonnullRefPtr<GUI::Widget> row_widget = group_box->add<GUI::Widget>();
  355. row_widget->set_fixed_height(22);
  356. property.role.visit(
  357. [&](Gfx::AlignmentRole role) {
  358. row_widget->load_from_gml(alignment_property_gml);
  359. auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
  360. name_label.set_text(to_string(role));
  361. auto& alignment_picker = *row_widget->find_descendant_of_type_named<GUI::ComboBox>("combo_box");
  362. alignment_picker.set_model(*m_alignment_model);
  363. alignment_picker.on_change = [&, role](auto&, auto& index) {
  364. set_alignment(role, index.data(GUI::ModelRole::Custom).to_text_alignment(Gfx::TextAlignment::CenterLeft));
  365. };
  366. alignment_picker.set_selected_index(m_alignment_model->index_of(m_current_palette.alignment(role)), GUI::AllowCallback::No);
  367. VERIFY(m_alignment_inputs[to_underlying(role)].is_null());
  368. m_alignment_inputs[to_underlying(role)] = alignment_picker;
  369. },
  370. [&](Gfx::ColorRole role) {
  371. row_widget->load_from_gml(color_property_gml);
  372. auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
  373. name_label.set_text(to_string(role));
  374. auto& color_input = *row_widget->find_descendant_of_type_named<GUI::ColorInput>("color_input");
  375. color_input.on_change = [&, role] {
  376. set_color(role, color_input.color());
  377. };
  378. color_input.set_color(m_current_palette.color(role), GUI::AllowCallback::No);
  379. VERIFY(m_color_inputs[to_underlying(role)].is_null());
  380. m_color_inputs[to_underlying(role)] = color_input;
  381. },
  382. [&](Gfx::FlagRole role) {
  383. row_widget->load_from_gml(flag_property_gml);
  384. auto& checkbox = *row_widget->find_descendant_of_type_named<GUI::CheckBox>("checkbox");
  385. checkbox.set_text(to_string(role));
  386. checkbox.on_checked = [&, role](bool checked) {
  387. set_flag(role, checked);
  388. };
  389. checkbox.set_checked(m_current_palette.flag(role), GUI::AllowCallback::No);
  390. VERIFY(m_flag_inputs[to_underlying(role)].is_null());
  391. m_flag_inputs[to_underlying(role)] = checkbox;
  392. },
  393. [&](Gfx::MetricRole role) {
  394. row_widget->load_from_gml(metric_property_gml);
  395. auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
  396. name_label.set_text(to_string(role));
  397. auto& spin_box = *row_widget->find_descendant_of_type_named<GUI::SpinBox>("spin_box");
  398. spin_box.on_change = [&, role](int value) {
  399. set_metric(role, value);
  400. };
  401. spin_box.set_value(m_current_palette.metric(role), GUI::AllowCallback::No);
  402. VERIFY(m_metric_inputs[to_underlying(role)].is_null());
  403. m_metric_inputs[to_underlying(role)] = spin_box;
  404. },
  405. [&](Gfx::PathRole role) {
  406. row_widget->load_from_gml(path_property_gml);
  407. auto& name_label = *row_widget->find_descendant_of_type_named<GUI::Label>("name");
  408. name_label.set_text(to_string(role));
  409. auto& path_input = *row_widget->find_descendant_of_type_named<GUI::TextBox>("path_input");
  410. path_input.on_change = [&, role] {
  411. set_path(role, path_input.text());
  412. };
  413. path_input.set_text(m_current_palette.path(role), GUI::AllowCallback::No);
  414. auto& path_picker_button = *row_widget->find_descendant_of_type_named<GUI::Button>("path_picker_button");
  415. auto picker_target = (role == Gfx::PathRole::TitleButtonIcons) ? PathPickerTarget::Folder : PathPickerTarget::File;
  416. path_picker_button.on_click = [&, role, picker_target](auto) {
  417. show_path_picker_dialog(to_string(role), path_input, picker_target);
  418. };
  419. VERIFY(m_path_inputs[to_underlying(role)].is_null());
  420. m_path_inputs[to_underlying(role)] = path_input;
  421. });
  422. }
  423. }
  424. }
  425. void MainWidget::set_alignment(Gfx::AlignmentRole role, Gfx::TextAlignment value)
  426. {
  427. auto preview_palette = m_current_palette;
  428. preview_palette.set_alignment(role, value);
  429. set_palette(preview_palette);
  430. }
  431. void MainWidget::set_color(Gfx::ColorRole role, Gfx::Color value)
  432. {
  433. auto preview_palette = m_current_palette;
  434. preview_palette.set_color(role, value);
  435. set_palette(preview_palette);
  436. }
  437. void MainWidget::set_flag(Gfx::FlagRole role, bool value)
  438. {
  439. auto preview_palette = m_current_palette;
  440. preview_palette.set_flag(role, value);
  441. set_palette(preview_palette);
  442. }
  443. void MainWidget::set_metric(Gfx::MetricRole role, int value)
  444. {
  445. auto preview_palette = m_current_palette;
  446. preview_palette.set_metric(role, value);
  447. set_palette(preview_palette);
  448. }
  449. void MainWidget::set_path(Gfx::PathRole role, DeprecatedString value)
  450. {
  451. auto preview_palette = m_current_palette;
  452. preview_palette.set_path(role, value);
  453. set_palette(preview_palette);
  454. }
  455. void MainWidget::set_palette(Gfx::Palette palette)
  456. {
  457. m_current_palette = move(palette);
  458. m_preview_widget->set_preview_palette(m_current_palette);
  459. m_theme_override_apply->set_enabled(true);
  460. window()->set_modified(true);
  461. }
  462. void MainWidget::show_path_picker_dialog(StringView property_display_name, GUI::TextBox& path_input, PathPickerTarget path_picker_target)
  463. {
  464. bool open_folder = path_picker_target == PathPickerTarget::Folder;
  465. auto window_title = DeprecatedString::formatted(open_folder ? "Select {} folder"sv : "Select {} file"sv, property_display_name);
  466. auto target_path = path_input.text();
  467. if (Core::File::exists(target_path)) {
  468. if (!Core::File::is_directory(target_path))
  469. target_path = LexicalPath::dirname(target_path);
  470. } else {
  471. target_path = "/res/icons";
  472. }
  473. auto result = GUI::FilePicker::get_open_filepath(window(), window_title, target_path, open_folder);
  474. if (!result.has_value())
  475. return;
  476. path_input.set_text(*result);
  477. }
  478. ErrorOr<void> MainWidget::load_from_file(Core::File& file)
  479. {
  480. auto config_file = TRY(Core::ConfigFile::open(file.filename(), file.leak_fd()));
  481. auto theme = TRY(Gfx::load_system_theme(config_file));
  482. VERIFY(theme.is_valid());
  483. auto new_palette = Gfx::Palette(Gfx::PaletteImpl::create_with_anonymous_buffer(theme));
  484. set_palette(move(new_palette));
  485. set_path(file.filename());
  486. #define __ENUMERATE_ALIGNMENT_ROLE(role) \
  487. if (auto alignment_input = m_alignment_inputs[to_underlying(Gfx::AlignmentRole::role)]) \
  488. alignment_input->set_selected_index(m_alignment_model->index_of(m_current_palette.alignment(Gfx::AlignmentRole::role)), GUI::AllowCallback::No);
  489. ENUMERATE_ALIGNMENT_ROLES(__ENUMERATE_ALIGNMENT_ROLE)
  490. #undef __ENUMERATE_ALIGNMENT_ROLE
  491. #define __ENUMERATE_COLOR_ROLE(role) \
  492. if (auto color_input = m_color_inputs[to_underlying(Gfx::ColorRole::role)]) \
  493. color_input->set_color(m_current_palette.color(Gfx::ColorRole::role), GUI::AllowCallback::No);
  494. ENUMERATE_COLOR_ROLES(__ENUMERATE_COLOR_ROLE)
  495. #undef __ENUMERATE_COLOR_ROLE
  496. #define __ENUMERATE_FLAG_ROLE(role) \
  497. if (auto flag_input = m_flag_inputs[to_underlying(Gfx::FlagRole::role)]) \
  498. flag_input->set_checked(m_current_palette.flag(Gfx::FlagRole::role), GUI::AllowCallback::No);
  499. ENUMERATE_FLAG_ROLES(__ENUMERATE_FLAG_ROLE)
  500. #undef __ENUMERATE_FLAG_ROLE
  501. #define __ENUMERATE_METRIC_ROLE(role) \
  502. if (auto metric_input = m_metric_inputs[to_underlying(Gfx::MetricRole::role)]) \
  503. metric_input->set_value(m_current_palette.metric(Gfx::MetricRole::role), GUI::AllowCallback::No);
  504. ENUMERATE_METRIC_ROLES(__ENUMERATE_METRIC_ROLE)
  505. #undef __ENUMERATE_METRIC_ROLE
  506. #define __ENUMERATE_PATH_ROLE(role) \
  507. if (auto path_input = m_path_inputs[to_underlying(Gfx::PathRole::role)]) \
  508. path_input->set_text(m_current_palette.path(Gfx::PathRole::role), GUI::AllowCallback::No);
  509. ENUMERATE_PATH_ROLES(__ENUMERATE_PATH_ROLE)
  510. #undef __ENUMERATE_PATH_ROLE
  511. m_last_modified_time = Time::now_monotonic();
  512. window()->set_modified(false);
  513. return {};
  514. }
  515. }