main.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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::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. auto& image_editor = create_new_editor(*image);
  88. layer_list_widget.set_image(image);
  89. image_editor.set_active_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 menubar = GUI::Menubar::construct();
  124. auto& file_menu = menubar->add_menu("&File");
  125. file_menu.add_action(new_image_action);
  126. file_menu.add_action(open_image_action);
  127. file_menu.add_action(save_image_as_action);
  128. auto& export_submenu = file_menu.add_submenu("&Export");
  129. export_submenu.add_action(
  130. GUI::Action::create(
  131. "As &BMP", [&](auto&) {
  132. auto* editor = current_image_editor();
  133. if (!editor)
  134. return;
  135. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
  136. if (!save_path.has_value())
  137. return;
  138. auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
  139. auto result = editor->image().export_bmp_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes);
  140. if (result.is_error())
  141. GUI::MessageBox::show_error(window, String::formatted("Export to BMP failed: {}", result.error()));
  142. },
  143. window));
  144. export_submenu.add_action(
  145. GUI::Action::create(
  146. "As &PNG", [&](auto&) {
  147. auto* editor = current_image_editor();
  148. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png");
  149. if (!save_path.has_value())
  150. return;
  151. auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
  152. auto result = editor->image().export_png_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes);
  153. if (result.is_error())
  154. GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error()));
  155. },
  156. window));
  157. file_menu.add_separator();
  158. file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
  159. GUI::Application::the()->quit();
  160. }));
  161. auto& edit_menu = menubar->add_menu("&Edit");
  162. auto copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
  163. auto* editor = current_image_editor();
  164. if (!editor)
  165. return;
  166. if (!editor->active_layer()) {
  167. dbgln("Cannot copy with no active layer selected");
  168. return;
  169. }
  170. auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
  171. if (!bitmap) {
  172. dbgln("try_copy_bitmap() from Layer failed");
  173. return;
  174. }
  175. GUI::Clipboard::the().set_bitmap(*bitmap);
  176. });
  177. auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
  178. auto* editor = current_image_editor();
  179. if (!editor)
  180. return;
  181. auto bitmap = GUI::Clipboard::the().bitmap();
  182. if (!bitmap)
  183. return;
  184. auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer");
  185. VERIFY(layer);
  186. editor->image().add_layer(*layer);
  187. editor->set_active_layer(layer);
  188. editor->selection().clear();
  189. });
  190. GUI::Clipboard::the().on_change = [&](auto& mime_type) {
  191. paste_action->set_enabled(mime_type == "image/x-serenityos");
  192. };
  193. paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
  194. edit_menu.add_action(copy_action);
  195. edit_menu.add_action(paste_action);
  196. auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
  197. if (auto* editor = current_image_editor())
  198. editor->undo();
  199. });
  200. edit_menu.add_action(undo_action);
  201. auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
  202. if (auto* editor = current_image_editor())
  203. editor->redo();
  204. });
  205. edit_menu.add_action(redo_action);
  206. edit_menu.add_separator();
  207. edit_menu.add_action(GUI::CommonActions::make_select_all_action([&](auto&) {
  208. auto* editor = current_image_editor();
  209. if (!editor->active_layer())
  210. return;
  211. editor->selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
  212. }));
  213. edit_menu.add_action(GUI::Action::create(
  214. "Clear &Selection", { Mod_Ctrl | Mod_Shift, Key_A }, [&](auto&) {
  215. if (auto* editor = current_image_editor())
  216. editor->selection().clear();
  217. },
  218. window));
  219. edit_menu.add_separator();
  220. edit_menu.add_action(GUI::Action::create(
  221. "&Swap Colors", { Mod_None, Key_X }, [&](auto&) {
  222. auto* editor = current_image_editor();
  223. if (!editor)
  224. return;
  225. auto old_primary_color = editor->primary_color();
  226. editor->set_primary_color(editor->secondary_color());
  227. editor->set_secondary_color(old_primary_color);
  228. },
  229. window));
  230. edit_menu.add_action(GUI::Action::create(
  231. "&Default Colors", { Mod_None, Key_D }, [&](auto&) {
  232. if (auto* editor = current_image_editor()) {
  233. editor->set_primary_color(Color::Black);
  234. editor->set_secondary_color(Color::White);
  235. }
  236. },
  237. window));
  238. edit_menu.add_action(GUI::Action::create(
  239. "&Load Color Palette", [&](auto&) {
  240. auto open_path = GUI::FilePicker::get_open_filepath(window, "Load Color Palette");
  241. if (!open_path.has_value())
  242. return;
  243. auto result = PixelPaint::PaletteWidget::load_palette_file(open_path.value());
  244. if (result.is_error()) {
  245. GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
  246. return;
  247. }
  248. palette_widget.display_color_list(result.value());
  249. },
  250. window));
  251. edit_menu.add_action(GUI::Action::create(
  252. "Sa&ve Color Palette", [&](auto&) {
  253. auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "palette");
  254. if (!save_path.has_value())
  255. return;
  256. auto result = PixelPaint::PaletteWidget::save_palette_file(palette_widget.colors(), save_path.value());
  257. if (result.is_error())
  258. GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error()));
  259. },
  260. window));
  261. auto& view_menu = menubar->add_menu("&View");
  262. auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
  263. [&](auto&) {
  264. if (auto* editor = current_image_editor())
  265. editor->scale_by(0.1f);
  266. },
  267. window);
  268. auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
  269. [&](auto&) {
  270. if (auto* editor = current_image_editor())
  271. editor->scale_by(-0.1f);
  272. },
  273. window);
  274. auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
  275. [&](auto&) {
  276. if (auto* editor = current_image_editor())
  277. editor->reset_scale_and_position();
  278. },
  279. window);
  280. view_menu.add_action(zoom_in_action);
  281. view_menu.add_action(zoom_out_action);
  282. view_menu.add_action(reset_zoom_action);
  283. auto& tool_menu = menubar->add_menu("&Tool");
  284. toolbox.for_each_tool([&](auto& tool) {
  285. if (tool.action())
  286. tool_menu.add_action(*tool.action());
  287. return IterationDecision::Continue;
  288. });
  289. auto& layer_menu = menubar->add_menu("&Layer");
  290. layer_menu.add_action(GUI::Action::create(
  291. "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
  292. auto* editor = current_image_editor();
  293. if (!editor)
  294. return;
  295. auto dialog = PixelPaint::CreateNewLayerDialog::construct(editor->image().size(), window);
  296. if (dialog->exec() == GUI::Dialog::ExecOK) {
  297. auto layer = PixelPaint::Layer::try_create_with_size(editor->image(), dialog->layer_size(), dialog->layer_name());
  298. if (!layer) {
  299. GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
  300. return;
  301. }
  302. editor->image().add_layer(layer.release_nonnull());
  303. editor->layers_did_change();
  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. editor->image().remove_layer(*active_layer);
  361. editor->set_active_layer(nullptr);
  362. },
  363. window));
  364. layer_list_widget.on_context_menu_request = [&](auto& event) {
  365. layer_menu.popup(event.screen_position());
  366. };
  367. auto& filter_menu = menubar->add_menu("&Filter");
  368. auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
  369. auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
  370. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
  371. auto* editor = current_image_editor();
  372. if (!editor)
  373. return;
  374. if (auto* layer = editor->active_layer()) {
  375. Gfx::LaplacianFilter filter;
  376. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
  377. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  378. editor->did_complete_action();
  379. }
  380. }
  381. }));
  382. edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
  383. auto* editor = current_image_editor();
  384. if (!editor)
  385. return;
  386. if (auto* layer = editor->active_layer()) {
  387. Gfx::LaplacianFilter filter;
  388. if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
  389. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  390. editor->did_complete_action();
  391. }
  392. }
  393. }));
  394. auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
  395. blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
  396. auto* editor = current_image_editor();
  397. if (!editor)
  398. return;
  399. if (auto* layer = editor->active_layer()) {
  400. Gfx::SpatialGaussianBlurFilter<3> filter;
  401. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
  402. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  403. editor->did_complete_action();
  404. }
  405. }
  406. }));
  407. blur_submenu.add_action(GUI::Action::create("G&aussian Blur (5x5)", [&](auto&) {
  408. auto* editor = current_image_editor();
  409. if (!editor)
  410. return;
  411. if (auto* layer = editor->active_layer()) {
  412. Gfx::SpatialGaussianBlurFilter<5> filter;
  413. if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
  414. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  415. editor->did_complete_action();
  416. }
  417. }
  418. }));
  419. blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
  420. auto* editor = current_image_editor();
  421. if (!editor)
  422. return;
  423. if (auto* layer = editor->active_layer()) {
  424. Gfx::BoxBlurFilter<3> filter;
  425. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
  426. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  427. editor->did_complete_action();
  428. }
  429. }
  430. }));
  431. blur_submenu.add_action(GUI::Action::create("B&ox Blur (5x5)", [&](auto&) {
  432. auto* editor = current_image_editor();
  433. if (!editor)
  434. return;
  435. if (auto* layer = editor->active_layer()) {
  436. Gfx::BoxBlurFilter<5> filter;
  437. if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
  438. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  439. editor->did_complete_action();
  440. }
  441. }
  442. }));
  443. blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
  444. auto* editor = current_image_editor();
  445. if (!editor)
  446. return;
  447. if (auto* layer = editor->active_layer()) {
  448. Gfx::SharpenFilter filter;
  449. if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
  450. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  451. editor->did_complete_action();
  452. }
  453. }
  454. }));
  455. spatial_filters_menu.add_separator();
  456. spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
  457. auto* editor = current_image_editor();
  458. if (!editor)
  459. return;
  460. if (auto* layer = editor->active_layer()) {
  461. Gfx::GenericConvolutionFilter<5> filter;
  462. if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
  463. filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
  464. editor->did_complete_action();
  465. }
  466. }
  467. }));
  468. auto& help_menu = menubar->add_menu("&Help");
  469. help_menu.add_action(GUI::CommonActions::make_about_action("Pixel Paint", app_icon, window));
  470. window->set_menubar(move(menubar));
  471. auto& toolbar = *main_widget.find_descendant_of_type_named<GUI::Toolbar>("toolbar");
  472. toolbar.add_action(new_image_action);
  473. toolbar.add_action(open_image_action);
  474. toolbar.add_action(save_image_as_action);
  475. toolbar.add_separator();
  476. toolbar.add_action(copy_action);
  477. toolbar.add_action(paste_action);
  478. toolbar.add_action(undo_action);
  479. toolbar.add_action(redo_action);
  480. toolbar.add_separator();
  481. toolbar.add_action(zoom_in_action);
  482. toolbar.add_action(zoom_out_action);
  483. toolbar.add_action(reset_zoom_action);
  484. create_new_editor = [&](NonnullRefPtr<PixelPaint::Image> image) -> PixelPaint::ImageEditor& {
  485. auto& image_editor = tab_widget.add_tab<PixelPaint::ImageEditor>("Untitled", image);
  486. image_editor.on_active_layer_change = [&](auto* layer) {
  487. if (current_image_editor() != &image_editor)
  488. return;
  489. layer_list_widget.set_selected_layer(layer);
  490. layer_properties_widget.set_layer(layer);
  491. };
  492. image_editor.on_image_title_change = [&](auto const& title) {
  493. tab_widget.set_tab_title(image_editor, title);
  494. };
  495. // NOTE: We invoke the above hook directly here to make sure the tab title is set up.
  496. image_editor.on_image_title_change(image->title());
  497. if (image->layer_count())
  498. image_editor.set_active_layer(&image->layer(0));
  499. tab_widget.set_active_widget(&image_editor);
  500. image_editor.set_focus(true);
  501. return image_editor;
  502. };
  503. tab_widget.on_tab_close_click = [&](auto& widget) {
  504. auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
  505. if (tab_widget.children().size() == 1) {
  506. layer_list_widget.set_image(nullptr);
  507. layer_properties_widget.set_layer(nullptr);
  508. }
  509. tab_widget.deferred_invoke([&](auto&) {
  510. tab_widget.remove_tab(image_editor);
  511. });
  512. };
  513. tab_widget.on_change = [&](auto& widget) {
  514. auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
  515. palette_widget.set_image_editor(image_editor);
  516. layer_list_widget.set_image(&image_editor.image());
  517. layer_properties_widget.set_layer(nullptr);
  518. // FIXME: This is badly factored. It transfers tools from the previously active editor to the new one.
  519. toolbox.template for_each_tool([&](auto& tool) {
  520. if (tool.editor()) {
  521. tool.editor()->set_active_tool(nullptr);
  522. image_editor.set_active_tool(&tool);
  523. }
  524. });
  525. };
  526. auto image_file_real_path = Core::File::real_path_for(image_file);
  527. if (Core::File::exists(image_file_real_path)) {
  528. open_image_file(image_file_real_path);
  529. } else {
  530. auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });
  531. auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
  532. VERIFY(bg_layer);
  533. image->add_layer(*bg_layer);
  534. bg_layer->bitmap().fill(Color::White);
  535. layer_list_widget.set_image(image);
  536. auto& editor = create_new_editor(*image);
  537. editor.set_active_layer(bg_layer);
  538. }
  539. auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
  540. app->on_action_enter = [&statusbar](GUI::Action& action) {
  541. auto text = action.status_tip();
  542. if (text.is_empty())
  543. text = Gfx::parse_ampersand_string(action.text());
  544. statusbar.set_override_text(move(text));
  545. };
  546. app->on_action_leave = [&statusbar](GUI::Action&) {
  547. statusbar.set_override_text({});
  548. };
  549. window->show();
  550. return app->exec();
  551. }