PresenterWidget.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /*
  2. * Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "PresenterWidget.h"
  7. #include "Presentation.h"
  8. #include <AK/Format.h>
  9. #include <LibCore/MimeData.h>
  10. #include <LibFileSystemAccessClient/Client.h>
  11. #include <LibGUI/Action.h>
  12. #include <LibGUI/Event.h>
  13. #include <LibGUI/Icon.h>
  14. #include <LibGUI/Menu.h>
  15. #include <LibGUI/MessageBox.h>
  16. #include <LibGUI/Painter.h>
  17. #include <LibGUI/Window.h>
  18. #include <LibGfx/Forward.h>
  19. #include <LibGfx/Orientation.h>
  20. PresenterWidget::PresenterWidget()
  21. {
  22. set_min_size(100, 100);
  23. }
  24. ErrorOr<void> PresenterWidget::initialize_menubar()
  25. {
  26. auto* window = this->window();
  27. // Set up the menu bar.
  28. auto& file_menu = window->add_menu("&File");
  29. auto open_action = GUI::CommonActions::make_open_action([this](auto&) {
  30. auto response = FileSystemAccessClient::Client::the().try_open_file(this->window());
  31. if (response.is_error())
  32. return;
  33. this->set_file(response.value()->filename());
  34. });
  35. auto about_action = GUI::CommonActions::make_about_action("Presenter", GUI::Icon::default_icon("app-display-settings"sv));
  36. TRY(file_menu.try_add_action(open_action));
  37. TRY(file_menu.try_add_action(about_action));
  38. auto& presentation_menu = window->add_menu("&Presentation");
  39. auto next_slide_action = GUI::Action::create("&Next", { KeyCode::Key_Right }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png"sv)), [this](auto&) {
  40. if (m_current_presentation) {
  41. m_current_presentation->next_frame();
  42. outln("Switched forward to slide {} frame {}", m_current_presentation->current_slide_number(), m_current_presentation->current_frame_in_slide_number());
  43. update();
  44. }
  45. });
  46. auto previous_slide_action = GUI::Action::create("&Previous", { KeyCode::Key_Left }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png"sv)), [this](auto&) {
  47. if (m_current_presentation) {
  48. m_current_presentation->previous_frame();
  49. outln("Switched backward to slide {} frame {}", m_current_presentation->current_slide_number(), m_current_presentation->current_frame_in_slide_number());
  50. update();
  51. }
  52. });
  53. TRY(presentation_menu.try_add_action(next_slide_action));
  54. TRY(presentation_menu.try_add_action(previous_slide_action));
  55. m_next_slide_action = next_slide_action;
  56. m_previous_slide_action = previous_slide_action;
  57. TRY(presentation_menu.try_add_action(GUI::Action::create("&Full Screen", { KeyModifier::Mod_Shift, KeyCode::Key_F5 }, { KeyCode::Key_F11 }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/fullscreen.png"sv)), [this](auto&) {
  58. this->window()->set_fullscreen(true);
  59. })));
  60. TRY(presentation_menu.try_add_action(GUI::Action::create("Present From First &Slide", { KeyCode::Key_F5 }, TRY(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/play.png"sv)), [this](auto&) {
  61. if (m_current_presentation)
  62. m_current_presentation->go_to_first_slide();
  63. this->window()->set_fullscreen(true);
  64. })));
  65. return {};
  66. }
  67. void PresenterWidget::set_file(StringView file_name)
  68. {
  69. auto presentation = Presentation::load_from_file(file_name, *window());
  70. if (presentation.is_error()) {
  71. GUI::MessageBox::show_error(window(), DeprecatedString::formatted("The presentation \"{}\" could not be loaded.\n{}", file_name, presentation.error()));
  72. } else {
  73. m_current_presentation = presentation.release_value();
  74. window()->set_title(DeprecatedString::formatted(title_template, m_current_presentation->title(), m_current_presentation->author()));
  75. set_min_size(m_current_presentation->normative_size());
  76. // This will apply the new minimum size.
  77. update();
  78. }
  79. }
  80. void PresenterWidget::keydown_event(GUI::KeyEvent& event)
  81. {
  82. if (event.key() == Key_Escape && window()->is_fullscreen())
  83. window()->set_fullscreen(false);
  84. // Alternate shortcuts for forward and backward
  85. switch (event.key()) {
  86. case Key_Down:
  87. case Key_PageDown:
  88. case Key_Space:
  89. case Key_N:
  90. case Key_Return:
  91. m_next_slide_action->activate();
  92. event.accept();
  93. break;
  94. case Key_Up:
  95. case Key_Backspace:
  96. case Key_PageUp:
  97. case Key_P:
  98. m_previous_slide_action->activate();
  99. event.accept();
  100. break;
  101. default:
  102. event.ignore();
  103. break;
  104. }
  105. }
  106. void PresenterWidget::paint_event([[maybe_unused]] GUI::PaintEvent& event)
  107. {
  108. if (!m_current_presentation)
  109. return;
  110. auto normative_size = m_current_presentation->normative_size();
  111. // Choose an aspect-correct size which doesn't exceed actual widget dimensions.
  112. auto width_corresponding_to_height = height() * normative_size.aspect_ratio();
  113. auto dimension_to_preserve = (width_corresponding_to_height > width()) ? Orientation::Horizontal : Orientation::Vertical;
  114. auto display_size = size().match_aspect_ratio(normative_size.aspect_ratio(), dimension_to_preserve);
  115. GUI::Painter painter { *this };
  116. auto clip_rect = Gfx::IntRect::centered_at({ width() / 2, height() / 2 }, display_size);
  117. painter.clear_clip_rect();
  118. // FIXME: This currently leaves a black border when the window aspect ratio doesn't match.
  119. // Figure out a way to apply the background color here as well.
  120. painter.add_clip_rect(clip_rect);
  121. m_current_presentation->paint(painter);
  122. }
  123. void PresenterWidget::drag_enter_event(GUI::DragEvent& event)
  124. {
  125. auto const& mime_types = event.mime_types();
  126. if (mime_types.contains_slow("text/uri-list"))
  127. event.accept();
  128. }
  129. void PresenterWidget::drop_event(GUI::DropEvent& event)
  130. {
  131. event.accept();
  132. if (event.mime_data().has_urls()) {
  133. auto urls = event.mime_data().urls();
  134. if (urls.is_empty())
  135. return;
  136. window()->move_to_front();
  137. set_file(urls.first().path());
  138. }
  139. }