main.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Julius Heijmen <julius.heijmen@gmail.com>
  4. * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <AK/URL.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibCore/File.h>
  11. #include <LibCore/System.h>
  12. #include <LibDesktop/Launcher.h>
  13. #include <LibGUI/Application.h>
  14. #include <LibGUI/FilePicker.h>
  15. #include <LibGUI/GML/AutocompleteProvider.h>
  16. #include <LibGUI/GML/Formatter.h>
  17. #include <LibGUI/GML/Lexer.h>
  18. #include <LibGUI/GML/SyntaxHighlighter.h>
  19. #include <LibGUI/Icon.h>
  20. #include <LibGUI/Menu.h>
  21. #include <LibGUI/Menubar.h>
  22. #include <LibGUI/MessageBox.h>
  23. #include <LibGUI/Painter.h>
  24. #include <LibGUI/RegularEditingEngine.h>
  25. #include <LibGUI/Splitter.h>
  26. #include <LibGUI/TextEditor.h>
  27. #include <LibGUI/VimEditingEngine.h>
  28. #include <LibGUI/Window.h>
  29. #include <LibMain/Main.h>
  30. #include <string.h>
  31. #include <unistd.h>
  32. namespace {
  33. class UnregisteredWidget final : public GUI::Widget {
  34. C_OBJECT(UnregisteredWidget);
  35. private:
  36. UnregisteredWidget(String const& class_name);
  37. virtual void paint_event(GUI::PaintEvent& event) override;
  38. String m_text;
  39. };
  40. UnregisteredWidget::UnregisteredWidget(String const& class_name)
  41. {
  42. StringBuilder builder;
  43. builder.append(class_name);
  44. builder.append("\nnot registered");
  45. m_text = builder.to_string();
  46. }
  47. void UnregisteredWidget::paint_event(GUI::PaintEvent& event)
  48. {
  49. GUI::Painter painter(*this);
  50. painter.add_clip_rect(event.rect());
  51. painter.fill_rect(event.rect(), Gfx::Color::DarkRed);
  52. painter.draw_text(rect(), m_text, Gfx::TextAlignment::Center, Color::White);
  53. }
  54. }
  55. ErrorOr<int> serenity_main(Main::Arguments arguments)
  56. {
  57. TRY(Core::System::pledge("stdio thread recvfd sendfd cpath rpath wpath unix"));
  58. auto app = TRY(GUI::Application::try_create(arguments));
  59. TRY(Desktop::Launcher::add_allowed_handler_with_only_specific_urls("/bin/Help", { URL::create_with_file_protocol("/usr/share/man/man1/Playground.md") }));
  60. TRY(Desktop::Launcher::seal_allowlist());
  61. TRY(Core::System::pledge("stdio thread recvfd sendfd rpath cpath wpath"));
  62. char const* path = nullptr;
  63. Core::ArgsParser args_parser;
  64. args_parser.add_positional_argument(path, "GML file to edit", "file", Core::ArgsParser::Required::No);
  65. args_parser.parse(arguments);
  66. auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-playground"));
  67. auto window = TRY(GUI::Window::try_create());
  68. window->set_title("GML Playground");
  69. window->set_icon(app_icon.bitmap_for_size(16));
  70. window->resize(800, 600);
  71. auto splitter = TRY(window->try_set_main_widget<GUI::HorizontalSplitter>());
  72. auto editor = TRY(splitter->try_add<GUI::TextEditor>());
  73. auto preview = TRY(splitter->try_add<GUI::Frame>());
  74. editor->set_syntax_highlighter(make<GUI::GML::SyntaxHighlighter>());
  75. editor->set_autocomplete_provider(make<GUI::GML::AutocompleteProvider>());
  76. editor->set_should_autocomplete_automatically(true);
  77. editor->set_automatic_indentation_enabled(true);
  78. editor->set_ruler_visible(true);
  79. String file_path;
  80. auto update_title = [&] {
  81. StringBuilder builder;
  82. if (file_path.is_empty())
  83. builder.append("Untitled");
  84. else
  85. builder.append(file_path);
  86. if (window->is_modified())
  87. builder.append("[*]");
  88. builder.append(" - GML Playground");
  89. window->set_title(builder.to_string());
  90. };
  91. if (String(path).is_empty()) {
  92. editor->set_text(R"~~~(@GUI::Frame {
  93. layout: @GUI::VerticalBoxLayout {
  94. }
  95. // Now add some widgets!
  96. }
  97. )~~~");
  98. editor->set_cursor(4, 28); // after "...widgets!"
  99. update_title();
  100. } else {
  101. auto file = Core::File::construct(path);
  102. if (!file->open(Core::OpenMode::ReadOnly)) {
  103. GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: {}", path, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
  104. return 1;
  105. }
  106. if (file->is_device()) {
  107. GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: Can't open device files", path), "Error", GUI::MessageBox::Type::Error);
  108. return 1;
  109. }
  110. file_path = path;
  111. editor->set_text(file->read_all());
  112. update_title();
  113. }
  114. editor->on_change = [&] {
  115. preview->remove_all_children();
  116. preview->load_from_gml(editor->text(), [](const String& class_name) -> RefPtr<Core::Object> {
  117. return UnregisteredWidget::construct(class_name);
  118. });
  119. };
  120. editor->on_modified_change = [&](bool modified) {
  121. window->set_modified(modified);
  122. update_title();
  123. };
  124. auto file_menu = TRY(window->try_add_menu("&File"));
  125. auto save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
  126. Optional<String> new_save_path = GUI::FilePicker::get_save_filepath(window, "Untitled", "gml");
  127. if (!new_save_path.has_value())
  128. return;
  129. if (!editor->write_to_file(new_save_path.value())) {
  130. GUI::MessageBox::show(window, "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error);
  131. return;
  132. }
  133. file_path = new_save_path.value();
  134. update_title();
  135. });
  136. auto save_action = GUI::CommonActions::make_save_action([&](auto&) {
  137. if (!file_path.is_empty()) {
  138. if (!editor->write_to_file(file_path)) {
  139. GUI::MessageBox::show(window, "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error);
  140. return;
  141. }
  142. update_title();
  143. return;
  144. }
  145. save_as_action->activate();
  146. });
  147. TRY(file_menu->try_add_action(GUI::CommonActions::make_open_action([&](auto&) {
  148. if (window->is_modified()) {
  149. auto result = GUI::MessageBox::ask_about_unsaved_changes(window, file_path, editor->document().undo_stack().last_unmodified_timestamp());
  150. if (result == GUI::MessageBox::ExecYes)
  151. save_action->activate();
  152. if (result != GUI::MessageBox::ExecNo && window->is_modified())
  153. return;
  154. }
  155. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
  156. if (!open_path.has_value())
  157. return;
  158. auto file = Core::File::construct(open_path.value());
  159. if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) {
  160. GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: {}", open_path.value(), strerror(errno)), "Error", GUI::MessageBox::Type::Error);
  161. return;
  162. }
  163. if (file->is_device()) {
  164. GUI::MessageBox::show(window, String::formatted("Opening \"{}\" failed: Can't open device files", open_path.value()), "Error", GUI::MessageBox::Type::Error);
  165. return;
  166. }
  167. file_path = open_path.value();
  168. editor->set_text(file->read_all());
  169. editor->set_focus(true);
  170. update_title();
  171. })));
  172. TRY(file_menu->try_add_action(save_action));
  173. TRY(file_menu->try_add_action(save_as_action));
  174. TRY(file_menu->try_add_separator());
  175. TRY(file_menu->try_add_action(GUI::CommonActions::make_quit_action([&](auto&) {
  176. if (window->on_close_request() == GUI::Window::CloseRequestDecision::Close)
  177. app->quit();
  178. })));
  179. auto edit_menu = TRY(window->try_add_menu("&Edit"));
  180. TRY(edit_menu->try_add_action(editor->undo_action()));
  181. TRY(edit_menu->try_add_action(editor->redo_action()));
  182. TRY(edit_menu->try_add_separator());
  183. TRY(edit_menu->try_add_action(editor->cut_action()));
  184. TRY(edit_menu->try_add_action(editor->copy_action()));
  185. TRY(edit_menu->try_add_action(editor->paste_action()));
  186. TRY(edit_menu->try_add_separator());
  187. TRY(edit_menu->try_add_action(editor->select_all_action()));
  188. TRY(edit_menu->try_add_action(editor->go_to_line_action()));
  189. TRY(edit_menu->try_add_separator());
  190. TRY(edit_menu->try_add_action(GUI::Action::create("&Format GML", { Mod_Ctrl | Mod_Shift, Key_I }, [&](auto&) {
  191. auto formatted_gml_or_error = GUI::GML::format_gml(editor->text());
  192. if (!formatted_gml_or_error.is_error()) {
  193. editor->replace_all_text_while_keeping_undo_stack(formatted_gml_or_error.release_value());
  194. } else {
  195. GUI::MessageBox::show(
  196. window,
  197. String::formatted("GML could not be formatted: {}", formatted_gml_or_error.error()),
  198. "Error",
  199. GUI::MessageBox::Type::Error);
  200. }
  201. })));
  202. auto vim_emulation_setting_action = GUI::Action::create_checkable("&Vim Emulation", { Mod_Ctrl | Mod_Shift | Mod_Alt, Key_V }, [&](auto& action) {
  203. if (action.is_checked())
  204. editor->set_editing_engine(make<GUI::VimEditingEngine>());
  205. else
  206. editor->set_editing_engine(make<GUI::RegularEditingEngine>());
  207. });
  208. vim_emulation_setting_action->set_checked(false);
  209. TRY(edit_menu->try_add_action(vim_emulation_setting_action));
  210. auto help_menu = TRY(window->try_add_menu("&Help"));
  211. TRY(help_menu->try_add_action(GUI::CommonActions::make_help_action([](auto&) {
  212. Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/Playground.md"), "/bin/Help");
  213. })));
  214. TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("GML Playground", app_icon, window)));
  215. window->on_close_request = [&] {
  216. if (!window->is_modified())
  217. return GUI::Window::CloseRequestDecision::Close;
  218. auto result = GUI::MessageBox::ask_about_unsaved_changes(window, file_path, editor->document().undo_stack().last_unmodified_timestamp());
  219. if (result == GUI::MessageBox::ExecYes) {
  220. save_action->activate();
  221. if (window->is_modified())
  222. return GUI::Window::CloseRequestDecision::StayOpen;
  223. return GUI::Window::CloseRequestDecision::Close;
  224. }
  225. if (result == GUI::MessageBox::ExecNo)
  226. return GUI::Window::CloseRequestDecision::Close;
  227. return GUI::Window::CloseRequestDecision::StayOpen;
  228. };
  229. window->show();
  230. return app->exec();
  231. }