main.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "CreateNewImageDialog.h"
  7. #include "CreateNewLayerDialog.h"
  8. #include "FilterParams.h"
  9. #include "Image.h"
  10. #include "ImageEditor.h"
  11. #include "Layer.h"
  12. #include "LayerListWidget.h"
  13. #include "LayerPropertiesWidget.h"
  14. #include "PaletteWidget.h"
  15. #include "Tool.h"
  16. #include "ToolPropertiesWidget.h"
  17. #include "ToolboxWidget.h"
  18. #include <Applications/PixelPaint/PixelPaintWindowGML.h>
  19. #include <LibCore/ArgsParser.h>
  20. #include <LibCore/File.h>
  21. #include <LibGUI/Action.h>
  22. #include <LibGUI/Application.h>
  23. #include <LibGUI/Clipboard.h>
  24. #include <LibGUI/FilePicker.h>
  25. #include <LibGUI/Icon.h>
  26. #include <LibGUI/Menubar.h>
  27. #include <LibGUI/MessageBox.h>
  28. #include <LibGUI/Statusbar.h>
  29. #include <LibGUI/TabWidget.h>
  30. #include <LibGUI/Toolbar.h>
  31. #include <LibGUI/Window.h>
  32. #include <LibGfx/Bitmap.h>
  33. #include <stdio.h>
  34. #include <unistd.h>
  35. int main(int argc, char** argv)
  36. {
  37. if (pledge("stdio thread recvfd sendfd rpath unix wpath cpath", nullptr) < 0) {
  38. perror("pledge");
  39. return 1;
  40. }
  41. auto app = GUI::Application::construct(argc, argv);
  42. const char* image_file = nullptr;
  43. Core::ArgsParser args_parser;
  44. args_parser.add_positional_argument(image_file, "Image file to open", "path", Core::ArgsParser::Required::No);
  45. args_parser.parse(argc, argv);
  46. auto app_icon = GUI::Icon::default_icon("app-pixel-paint");
  47. auto window = GUI::Window::construct();
  48. window->set_title("Pixel Paint");
  49. window->resize(800, 510);
  50. window->set_icon(app_icon.bitmap_for_size(16));
  51. auto& main_widget = window->set_main_widget<GUI::Widget>();
  52. main_widget.load_from_gml(pixel_paint_window_gml);
  53. auto& toolbox = *main_widget.find_descendant_of_type_named<PixelPaint::ToolboxWidget>("toolbox");
  54. auto& tab_widget = *main_widget.find_descendant_of_type_named<GUI::TabWidget>("tab_widget");
  55. tab_widget.set_container_margins({ 4, 4, 5, 5 });
  56. tab_widget.set_close_button_enabled(true);
  57. auto& palette_widget = *main_widget.find_descendant_of_type_named<PixelPaint::PaletteWidget>("palette_widget");
  58. auto current_image_editor = [&]() -> PixelPaint::ImageEditor* {
  59. if (!tab_widget.active_widget())
  60. return nullptr;
  61. return verify_cast<PixelPaint::ImageEditor>(tab_widget.active_widget());
  62. };
  63. Function<PixelPaint::ImageEditor&(NonnullRefPtr<PixelPaint::Image>)> create_new_editor;
  64. auto& layer_list_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerListWidget>("layer_list_widget");
  65. layer_list_widget.on_layer_select = [&](auto* layer) {
  66. if (auto* editor = current_image_editor())
  67. editor->set_active_layer(layer);
  68. };
  69. auto& layer_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerPropertiesWidget>("layer_properties_widget");
  70. auto& tool_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::ToolPropertiesWidget>("tool_properties_widget");
  71. toolbox.on_tool_selection = [&](auto* tool) {
  72. if (auto* editor = current_image_editor())
  73. editor->set_active_tool(tool);
  74. tool_properties_widget.set_active_tool(tool);
  75. };
  76. auto new_image_action = GUI::Action::create(
  77. "&New Image...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/new.png"), [&](auto&) {
  78. auto dialog = PixelPaint::CreateNewImageDialog::construct(window);
  79. if (dialog->exec() == GUI::Dialog::ExecOK) {
  80. auto image = PixelPaint::Image::try_create_with_size(dialog->image_size());
  81. auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
  82. VERIFY(bg_layer);
  83. image->add_layer(*bg_layer);
  84. bg_layer->bitmap().fill(Color::White);
  85. auto image_title = dialog->image_name().trim_whitespace();
  86. image->set_title(image_title.is_empty() ? "Untitled" : image_title);
  87. create_new_editor(*image);
  88. layer_list_widget.set_image(image);
  89. layer_list_widget.set_selected_layer(bg_layer);
  90. }
  91. },
  92. window);
  93. auto open_image_file = [&](auto& path) {
  94. auto image_or_error = PixelPaint::Image::try_create_from_file(path);
  95. if (image_or_error.is_error()) {
  96. GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
  97. return;
  98. }
  99. auto& image = *image_or_error.value();
  100. create_new_editor(image);
  101. layer_list_widget.set_image(&image);
  102. };
  103. auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
  104. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
  105. if (!open_path.has_value())
  106. return;
  107. open_image_file(open_path.value());
  108. });
  109. auto save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  110. auto* editor = current_image_editor();
  111. if (!editor)
  112. return;
  113. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp");
  114. if (!save_path.has_value())
  115. return;
  116. auto result = editor->image().write_to_file(save_path.value());
  117. if (result.is_error()) {
  118. GUI::MessageBox::show_error(window, String::formatted("Could not save {}: {}", save_path.value(), result.error()));
  119. return;
  120. }
  121. editor->image().set_path(save_path.value());
  122. });
  123. auto& file_menu = window->add_menu("&File");
  124. file_menu.add_action(new_image_action);
  125. file_menu.add_action(open_image_action);
  126. file_menu.add_action(save_image_as_action);
  127. auto& export_submenu = file_menu.add_submenu("&Export");
  128. export_submenu.add_action(
  129. GUI::Action::create(
  130. "As &BMP", [&](auto&) {
  131. auto* editor = current_image_editor();
  132. if (!editor)
  133. return;
  134. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
  135. if (!save_path.has_value())
  136. return;
  137. auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
  138. auto result = editor->image().export_bmp_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes);
  139. if (result.is_error())
  140. GUI::MessageBox::show_error(window, String::formatted("Export to BMP failed: {}", result.error()));
  141. },
  142. window));
  143. export_submenu.add_action(
  144. GUI::Action::create(
  145. "As &PNG", [&](auto&) {
  146. auto* editor = current_image_editor();
  147. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png");
  148. if (!save_path.has_value())
  149. return;
  150. auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
  151. auto result = editor->image().export_png_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes);
  152. if (result.is_error())
  153. GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error()));
  154. },
  155. window));
  156. file_menu.add_separator();
  157. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  158. GUI::Application::the()->quit();
  159. }));
  160. auto& edit_menu = window->add_menu("&Edit");
  161. auto copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  162. auto* editor = current_image_editor();
  163. if (!editor)
  164. return;
  165. if (!editor->active_layer()) {
  166. dbgln("Cannot copy with no active layer selected");
  167. return;
  168. }
  169. auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
  170. if (!bitmap) {
  171. dbgln("try_copy_bitmap() from Layer failed");
  172. return;
  173. }
  174. GUI::Clipboard::the().set_bitmap(*bitmap);
  175. });
  176. auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  177. auto* editor = current_image_editor();
  178. if (!editor)
  179. return;
  180. auto bitmap = GUI::Clipboard::the().bitmap();
  181. if (!bitmap)
  182. return;
  183. auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer");
  184. VERIFY(layer);
  185. editor->image().add_layer(*layer);
  186. editor->set_active_layer(layer);
  187. editor->selection().clear();
  188. });
  189. GUI::Clipboard::the().on_change = [&](auto& mime_type) {
  190. paste_action->set_enabled(mime_type == "image/x-serenityos");
  191. };
  192. paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
  193. edit_menu.add_action(copy_action);
  194. edit_menu.add_action(paste_action);
  195. auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  196. if (auto* editor = current_image_editor())
  197. editor->undo();
  198. });
  199. edit_menu.add_action(undo_action);
  200. auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  201. if (auto* editor = current_image_editor())
  202. editor->redo();
  203. });
  204. edit_menu.add_action(redo_action);
  205. edit_menu.add_separator();
  206. edit_menu.add_action(GUI::CommonActions::make_select_all_action([&](auto&) {
  207. auto* editor = current_image_editor();
  208. if (!editor->active_layer())
  209. return;
  210. editor->selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
  211. }));
  212. edit_menu.add_action(GUI::Action::create(
  213. "Clear &Selection", { Mod_Ctrl | Mod_Shift, Key_A }, [&](auto&) {
  214. if (auto* editor = current_image_editor())
  215. editor->selection().clear();
  216. },
  217. window));
  218. edit_menu.add_separator();
  219. edit_menu.add_action(GUI::Action::create(
  220. "&Swap Colors", { Mod_None, Key_X }, [&](auto&) {
  221. auto* editor = current_image_editor();
  222. if (!editor)
  223. return;
  224. auto old_primary_color = editor->primary_color();
  225. editor->set_primary_color(editor->secondary_color());
  226. editor->set_secondary_color(old_primary_color);
  227. },
  228. window));
  229. edit_menu.add_action(GUI::Action::create(
  230. "&Default Colors", { Mod_None, Key_D }, [&](auto&) {
  231. if (auto* editor = current_image_editor()) {
  232. editor->set_primary_color(Color::Black);
  233. editor->set_secondary_color(Color::White);
  234. }
  235. },
  236. window));
  237. edit_menu.add_action(GUI::Action::create(
  238. "&Load Color Palette", [&](auto&) {
  239. auto open_path = GUI::FilePicker::get_open_filepath(window, "Load Color Palette");
  240. if (!open_path.has_value())
  241. return;
  242. auto result = PixelPaint::PaletteWidget::load_palette_file(open_path.value());
  243. if (result.is_error()) {
  244. GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
  245. return;
  246. }
  247. palette_widget.display_color_list(result.value());
  248. },
  249. window));
  250. edit_menu.add_action(GUI::Action::create(
  251. "Sa&ve Color Palette", [&](auto&) {
  252. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "palette");
  253. if (!save_path.has_value())
  254. return;
  255. auto result = PixelPaint::PaletteWidget::save_palette_file(palette_widget.colors(), save_path.value());
  256. if (result.is_error())
  257. GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error()));
  258. },
  259. window));
  260. auto& view_menu = window->add_menu("&View");
  261. auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
  262. [&](auto&) {
  263. if (auto* editor = current_image_editor())
  264. editor->scale_by(0.1f);
  265. },
  266. window);
  267. auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
  268. [&](auto&) {
  269. if (auto* editor = current_image_editor())
  270. editor->scale_by(-0.1f);
  271. },
  272. window);
  273. auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
  274. [&](auto&) {
  275. if (auto* editor = current_image_editor())
  276. editor->reset_scale_and_position();
  277. },
  278. window);
  279. view_menu.add_action(zoom_in_action);
  280. view_menu.add_action(zoom_out_action);
  281. view_menu.add_action(reset_zoom_action);
  282. auto& tool_menu = window->add_menu("&Tool");
  283. toolbox.for_each_tool([&](auto& tool) {
  284. if (tool.action())
  285. tool_menu.add_action(*tool.action());
  286. return IterationDecision::Continue;
  287. });
  288. auto& layer_menu = window->add_menu("&Layer");
  289. layer_menu.add_action(GUI::Action::create(
  290. "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
  291. auto* editor = current_image_editor();
  292. if (!editor)
  293. return;
  294. auto dialog = PixelPaint::CreateNewLayerDialog::construct(editor->image().size(), window);
  295. if (dialog->exec() == GUI::Dialog::ExecOK) {
  296. auto layer = PixelPaint::Layer::try_create_with_size(editor->image(), dialog->layer_size(), dialog->layer_name());
  297. if (!layer) {
  298. GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
  299. return;
  300. }
  301. editor->image().add_layer(layer.release_nonnull());
  302. editor->layers_did_change();
  303. layer_list_widget.select_top_layer();
  304. }
  305. },
  306. window));
  307. layer_menu.add_separator();
  308. layer_menu.add_action(GUI::Action::create(
  309. "Select &Previous Layer", { 0, Key_PageUp }, [&](auto&) {
  310. layer_list_widget.cycle_through_selection(1);
  311. },
  312. window));
  313. layer_menu.add_action(GUI::Action::create(
  314. "Select &Next Layer", { 0, Key_PageDown }, [&](auto&) {
  315. layer_list_widget.cycle_through_selection(-1);
  316. },
  317. window));
  318. layer_menu.add_action(GUI::Action::create(
  319. "Select &Top Layer", { 0, Key_Home }, [&](auto&) {
  320. layer_list_widget.select_top_layer();
  321. },
  322. window));
  323. layer_menu.add_action(GUI::Action::create(
  324. "Select &Bottom Layer", { 0, Key_End }, [&](auto&) {
  325. layer_list_widget.select_bottom_layer();
  326. },
  327. window));
  328. layer_menu.add_separator();
  329. layer_menu.add_action(GUI::Action::create(
  330. "Move Active Layer &Up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
  331. auto* editor = current_image_editor();
  332. if (!editor)
  333. return;
  334. auto active_layer = editor->active_layer();
  335. if (!active_layer)
  336. return;
  337. editor->image().move_layer_up(*active_layer);
  338. },
  339. window));
  340. layer_menu.add_action(GUI::Action::create(
  341. "Move Active Layer &Down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
  342. auto* editor = current_image_editor();
  343. if (!editor)
  344. return;
  345. auto active_layer = editor->active_layer();
  346. if (!active_layer)
  347. return;
  348. editor->image().move_layer_down(*active_layer);
  349. },
  350. window));
  351. layer_menu.add_separator();
  352. layer_menu.add_action(GUI::Action::create(
  353. "&Remove Active Layer", { Mod_Ctrl, Key_D }, [&](auto&) {
  354. auto* editor = current_image_editor();
  355. if (!editor)
  356. return;
  357. auto active_layer = editor->active_layer();
  358. if (!active_layer)
  359. return;
  360. auto active_layer_index = editor->image().index_of(*active_layer);
  361. editor->image().remove_layer(*active_layer);
  362. if (editor->image().layer_count()) {
  363. auto& next_active_layer = editor->image().layer(active_layer_index > 0 ? active_layer_index - 1 : 0);
  364. editor->set_active_layer(&next_active_layer);
  365. } else {
  366. editor->set_active_layer(nullptr);
  367. }
  368. },
  369. window));
  370. layer_list_widget.on_context_menu_request = [&](auto& event) {
  371. layer_menu.popup(event.screen_position());
  372. };
  373. layer_menu.add_separator();
  374. layer_menu.add_action(GUI::Action::create(
  375. "&Flatten Image", { Mod_Ctrl, Key_F }, [&](auto&) {
  376. auto* editor = current_image_editor();
  377. if (!editor)
  378. return;
  379. editor->image().flatten_all_layers();
  380. editor->did_complete_action();
  381. },
  382. window));
  383. layer_menu.add_action(GUI::Action::create(
  384. "&Merge Visible", { Mod_Ctrl, Key_M }, [&](auto&) {
  385. auto* editor = current_image_editor();
  386. if (!editor)
  387. return;
  388. editor->image().merge_visible_layers();
  389. editor->did_complete_action();
  390. },
  391. window));
  392. auto& filter_menu = window->add_menu("&Filter");
  393. auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
  394. auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
  395. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
  396. auto* editor = current_image_editor();
  397. if (!editor)
  398. return;
  399. if (auto* layer = editor->active_layer()) {
  400. Gfx::LaplacianFilter filter;
  401. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
  402. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  403. editor->did_complete_action();
  404. }
  405. }
  406. }));
  407. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
  408. auto* editor = current_image_editor();
  409. if (!editor)
  410. return;
  411. if (auto* layer = editor->active_layer()) {
  412. Gfx::LaplacianFilter filter;
  413. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
  414. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  415. editor->did_complete_action();
  416. }
  417. }
  418. }));
  419. auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
  420. blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
  421. auto* editor = current_image_editor();
  422. if (!editor)
  423. return;
  424. if (auto* layer = editor->active_layer()) {
  425. Gfx::SpatialGaussianBlurFilter<3> filter;
  426. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
  427. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  428. editor->did_complete_action();
  429. }
  430. }
  431. }));
  432. blur_submenu.add_action(GUI::Action::create("G&aussian Blur (5x5)", [&](auto&) {
  433. auto* editor = current_image_editor();
  434. if (!editor)
  435. return;
  436. if (auto* layer = editor->active_layer()) {
  437. Gfx::SpatialGaussianBlurFilter<5> filter;
  438. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
  439. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  440. editor->did_complete_action();
  441. }
  442. }
  443. }));
  444. blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
  445. auto* editor = current_image_editor();
  446. if (!editor)
  447. return;
  448. if (auto* layer = editor->active_layer()) {
  449. Gfx::BoxBlurFilter<3> filter;
  450. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
  451. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  452. editor->did_complete_action();
  453. }
  454. }
  455. }));
  456. blur_submenu.add_action(GUI::Action::create("B&ox Blur (5x5)", [&](auto&) {
  457. auto* editor = current_image_editor();
  458. if (!editor)
  459. return;
  460. if (auto* layer = editor->active_layer()) {
  461. Gfx::BoxBlurFilter<5> filter;
  462. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
  463. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  464. editor->did_complete_action();
  465. }
  466. }
  467. }));
  468. blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
  469. auto* editor = current_image_editor();
  470. if (!editor)
  471. return;
  472. if (auto* layer = editor->active_layer()) {
  473. Gfx::SharpenFilter filter;
  474. if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
  475. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  476. editor->did_complete_action();
  477. }
  478. }
  479. }));
  480. spatial_filters_menu.add_separator();
  481. spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
  482. auto* editor = current_image_editor();
  483. if (!editor)
  484. return;
  485. if (auto* layer = editor->active_layer()) {
  486. Gfx::GenericConvolutionFilter<5> filter;
  487. if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
  488. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  489. editor->did_complete_action();
  490. }
  491. }
  492. }));
  493. auto& help_menu = window->add_menu("&Help");
  494. help_menu.add_action(GUI::CommonActions::make_about_action("Pixel Paint", app_icon, window));
  495. auto& toolbar = *main_widget.find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  496. toolbar.add_action(new_image_action);
  497. toolbar.add_action(open_image_action);
  498. toolbar.add_action(save_image_as_action);
  499. toolbar.add_separator();
  500. toolbar.add_action(copy_action);
  501. toolbar.add_action(paste_action);
  502. toolbar.add_action(undo_action);
  503. toolbar.add_action(redo_action);
  504. toolbar.add_separator();
  505. toolbar.add_action(zoom_in_action);
  506. toolbar.add_action(zoom_out_action);
  507. toolbar.add_action(reset_zoom_action);
  508. create_new_editor = [&](NonnullRefPtr<PixelPaint::Image> image) -> PixelPaint::ImageEditor& {
  509. auto& image_editor = tab_widget.add_tab<PixelPaint::ImageEditor>("Untitled", image);
  510. image_editor.on_active_layer_change = [&](auto* layer) {
  511. if (current_image_editor() != &image_editor)
  512. return;
  513. layer_list_widget.set_selected_layer(layer);
  514. layer_properties_widget.set_layer(layer);
  515. };
  516. image_editor.on_image_title_change = [&](auto const& title) {
  517. tab_widget.set_tab_title(image_editor, title);
  518. };
  519. auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  520. image_editor.on_image_mouse_position_change = [&](auto const& mouse_position) {
  521. auto const& image_size = current_image_editor()->image().size();
  522. auto image_rectangle = Gfx::IntRect { 0, 0, image_size.width(), image_size.height() };
  523. if (image_rectangle.contains(mouse_position)) {
  524. statusbar.set_override_text(mouse_position.to_string());
  525. } else {
  526. statusbar.set_override_text({});
  527. }
  528. };
  529. image_editor.on_leave = [&]() {
  530. statusbar.set_override_text({});
  531. };
  532. // NOTE: We invoke the above hook directly here to make sure the tab title is set up.
  533. image_editor.on_image_title_change(image->title());
  534. if (image->layer_count())
  535. image_editor.set_active_layer(&image->layer(0));
  536. tab_widget.set_active_widget(&image_editor);
  537. image_editor.set_focus(true);
  538. return image_editor;
  539. };
  540. tab_widget.on_tab_close_click = [&](auto& widget) {
  541. auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
  542. if (tab_widget.children().size() == 1) {
  543. layer_list_widget.set_image(nullptr);
  544. layer_properties_widget.set_layer(nullptr);
  545. }
  546. tab_widget.deferred_invoke([&](auto&) {
  547. tab_widget.remove_tab(image_editor);
  548. });
  549. };
  550. tab_widget.on_change = [&](auto& widget) {
  551. auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
  552. palette_widget.set_image_editor(image_editor);
  553. layer_list_widget.set_image(&image_editor.image());
  554. layer_properties_widget.set_layer(image_editor.active_layer());
  555. // FIXME: This is badly factored. It transfers tools from the previously active editor to the new one.
  556. toolbox.template for_each_tool([&](auto& tool) {
  557. if (tool.editor()) {
  558. tool.editor()->set_active_tool(nullptr);
  559. image_editor.set_active_tool(&tool);
  560. }
  561. });
  562. };
  563. auto image_file_real_path = Core::File::real_path_for(image_file);
  564. if (Core::File::exists(image_file_real_path)) {
  565. open_image_file(image_file_real_path);
  566. } else {
  567. auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });
  568. auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
  569. VERIFY(bg_layer);
  570. image->add_layer(*bg_layer);
  571. bg_layer->bitmap().fill(Color::White);
  572. layer_list_widget.set_image(image);
  573. auto& editor = create_new_editor(*image);
  574. editor.set_active_layer(bg_layer);
  575. }
  576. auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  577. app->on_action_enter = [&statusbar](GUI::Action& action) {
  578. auto text = action.status_tip();
  579. if (text.is_empty())
  580. text = Gfx::parse_ampersand_string(action.text());
  581. statusbar.set_override_text(move(text));
  582. };
  583. app->on_action_leave = [&statusbar](GUI::Action&) {
  584. statusbar.set_override_text({});
  585. };
  586. window->show();
  587. return app->exec();
  588. }