PresenterWidget.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
  3. * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "PresenterWidget.h"
  8. #include "Presentation.h"
  9. #include <LibCore/MimeData.h>
  10. #include <LibFileSystemAccessClient/Client.h>
  11. #include <LibGUI/Action.h>
  12. #include <LibGUI/Application.h>
  13. #include <LibGUI/Event.h>
  14. #include <LibGUI/Icon.h>
  15. #include <LibGUI/Menu.h>
  16. #include <LibGUI/MessageBox.h>
  17. #include <LibGUI/Painter.h>
  18. PresenterWidget::PresenterWidget()
  19. {
  20. set_min_size(200, 120);
  21. set_fill_with_background_color(true);
  22. m_web_view = add<WebView::OutOfProcessWebView>();
  23. m_web_view->set_frame_style(Gfx::FrameStyle::NoFrame);
  24. m_web_view->set_scrollbars_enabled(false);
  25. m_web_view->set_focus_policy(GUI::FocusPolicy::NoFocus);
  26. m_web_view->set_content_scales_to_viewport(true);
  27. }
  28. void PresenterWidget::resize_event(GUI::ResizeEvent& event)
  29. {
  30. Widget::resize_event(event);
  31. if (!m_current_presentation)
  32. return;
  33. auto normative_size = m_current_presentation->normative_size().to_type<float>();
  34. float widget_ratio = static_cast<float>(event.size().width()) / static_cast<float>(event.size().height());
  35. float wh_ratio = normative_size.width() / normative_size.height();
  36. Gfx::IntRect rect;
  37. if (widget_ratio >= wh_ratio) {
  38. rect.set_width(static_cast<int>(ceilf(static_cast<float>(event.size().height()) * wh_ratio)));
  39. rect.set_height(event.size().height());
  40. } else {
  41. float hw_ratio = normative_size.height() / normative_size.width();
  42. rect.set_width(event.size().width());
  43. rect.set_height(static_cast<int>(ceilf(static_cast<float>(event.size().width()) * hw_ratio)));
  44. }
  45. m_web_view->set_relative_rect(rect.centered_within(this->rect()));
  46. }
  47. ErrorOr<void> PresenterWidget::initialize_menubar()
  48. {
  49. auto* window = this->window();
  50. // Set up the menu bar.
  51. auto file_menu = window->add_menu("&File"_string);
  52. auto open_action = GUI::CommonActions::make_open_action([this](auto&) {
  53. FileSystemAccessClient::OpenFileOptions options {
  54. .allowed_file_types = { { GUI::FileTypeFilter { "Presentation Files", { { "presenter" } } }, GUI::FileTypeFilter::all_files() } },
  55. };
  56. auto response = FileSystemAccessClient::Client::the().open_file(this->window(), options);
  57. if (response.is_error())
  58. return;
  59. this->set_file(response.value().filename());
  60. });
  61. file_menu->add_action(open_action);
  62. file_menu->add_separator();
  63. file_menu->add_action(GUI::CommonActions::make_quit_action([](auto&) {
  64. GUI::Application::the()->quit();
  65. }));
  66. auto presentation_menu = window->add_menu("&Presentation"_string);
  67. m_next_slide_action = GUI::Action::create("&Next", { KeyCode::Key_Right }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv)), [this](auto&) {
  68. if (m_current_presentation) {
  69. m_current_presentation->next_frame();
  70. update_web_view();
  71. update_slides_actions();
  72. }
  73. });
  74. m_previous_slide_action = GUI::Action::create("&Previous", { KeyCode::Key_Left }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv)), [this](auto&) {
  75. if (m_current_presentation) {
  76. m_current_presentation->previous_frame();
  77. update_web_view();
  78. update_slides_actions();
  79. }
  80. });
  81. m_present_from_first_slide_action = GUI::Action::create("Present from First &Slide", { KeyCode::Key_F5 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"sv)), [this](auto&) {
  82. if (m_current_presentation) {
  83. m_current_presentation->go_to_first_slide();
  84. update_web_view();
  85. }
  86. this->window()->set_fullscreen(true);
  87. });
  88. presentation_menu->add_action(*m_next_slide_action);
  89. presentation_menu->add_action(*m_previous_slide_action);
  90. presentation_menu->add_action(*m_present_from_first_slide_action);
  91. auto view_menu = window->add_menu("&View"_string);
  92. m_full_screen_action = GUI::Action::create("Toggle &Full Screen", { KeyModifier::Mod_Shift, KeyCode::Key_F5 }, { KeyCode::Key_F11 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/fullscreen.png"sv)), [this](auto&) {
  93. auto* window = this->window();
  94. window->set_fullscreen(!window->is_fullscreen());
  95. });
  96. m_resize_to_fit_content_action = GUI::Action::create("Resize to Fit &Content", { KeyModifier::Mod_Alt, KeyCode::Key_C }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/scale.png"sv)), [this](auto&) {
  97. if (m_current_presentation) {
  98. auto presentation_size = m_current_presentation->normative_size();
  99. auto* window = this->window();
  100. window->resize(window->size().match_aspect_ratio(presentation_size.aspect_ratio(), Orientation::Horizontal));
  101. }
  102. });
  103. view_menu->add_action(*m_full_screen_action);
  104. view_menu->add_action(*m_resize_to_fit_content_action);
  105. update_slides_actions();
  106. auto help_menu = window->add_menu("&Help"_string);
  107. help_menu->add_action(GUI::CommonActions::make_about_action("Presenter"_string, GUI::Icon::default_icon("app-presenter"sv)));
  108. return {};
  109. }
  110. void PresenterWidget::update_web_view()
  111. {
  112. m_web_view->run_javascript(DeprecatedString::formatted("goto({}, {})", m_current_presentation->current_slide_number(), m_current_presentation->current_frame_in_slide_number()));
  113. }
  114. void PresenterWidget::update_slides_actions()
  115. {
  116. if (m_current_presentation) {
  117. m_next_slide_action->set_enabled(m_current_presentation->has_next_frame());
  118. m_previous_slide_action->set_enabled(m_current_presentation->has_previous_frame());
  119. m_full_screen_action->set_enabled(true);
  120. m_present_from_first_slide_action->set_enabled(true);
  121. } else {
  122. m_next_slide_action->set_enabled(false);
  123. m_previous_slide_action->set_enabled(false);
  124. m_full_screen_action->set_enabled(false);
  125. m_present_from_first_slide_action->set_enabled(false);
  126. }
  127. }
  128. void PresenterWidget::set_file(StringView file_name)
  129. {
  130. auto presentation = Presentation::load_from_file(file_name);
  131. if (presentation.is_error()) {
  132. GUI::MessageBox::show_error(window(), DeprecatedString::formatted("The presentation \"{}\" could not be loaded.\n{}", file_name, presentation.error()));
  133. } else {
  134. m_current_presentation = presentation.release_value();
  135. window()->set_title(DeprecatedString::formatted(title_template, m_current_presentation->title(), m_current_presentation->author()));
  136. set_min_size(m_current_presentation->normative_size());
  137. m_web_view->load_html(MUST(m_current_presentation->render()), "presenter://slide.html"sv);
  138. update_slides_actions();
  139. }
  140. }
  141. void PresenterWidget::keydown_event(GUI::KeyEvent& event)
  142. {
  143. if (event.key() == Key_Escape && window()->is_fullscreen())
  144. window()->set_fullscreen(false);
  145. // Alternate shortcuts for forward and backward
  146. switch (event.key()) {
  147. case Key_Down:
  148. case Key_PageDown:
  149. case Key_Space:
  150. case Key_N:
  151. case Key_Return:
  152. m_next_slide_action->activate();
  153. event.accept();
  154. break;
  155. case Key_Up:
  156. case Key_Backspace:
  157. case Key_PageUp:
  158. case Key_P:
  159. m_previous_slide_action->activate();
  160. event.accept();
  161. break;
  162. default:
  163. event.ignore();
  164. break;
  165. }
  166. }
  167. void PresenterWidget::paint_event(GUI::PaintEvent& event)
  168. {
  169. GUI::Painter painter(*this);
  170. painter.clear_rect(event.rect(), Gfx::Color::Black);
  171. }
  172. void PresenterWidget::second_paint_event(GUI::PaintEvent& event)
  173. {
  174. if (!m_current_presentation)
  175. return;
  176. GUI::Painter painter(*this);
  177. painter.add_clip_rect(event.rect());
  178. painter.draw_text(m_web_view->relative_rect(), m_current_presentation->current_slide().title(), Gfx::TextAlignment::BottomCenter);
  179. }
  180. void PresenterWidget::drag_enter_event(GUI::DragEvent& event)
  181. {
  182. auto const& mime_types = event.mime_types();
  183. if (mime_types.contains_slow("text/uri-list"))
  184. event.accept();
  185. }
  186. void PresenterWidget::drop_event(GUI::DropEvent& event)
  187. {
  188. event.accept();
  189. if (event.mime_data().has_urls()) {
  190. auto urls = event.mime_data().urls();
  191. if (urls.is_empty())
  192. return;
  193. window()->move_to_front();
  194. set_file(urls.first().serialize_path());
  195. }
  196. }