From 5d8d9b337a9f048c4e4bb376d7b2d5de7059ebac Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 1 Sep 2025 08:20:14 -0400 Subject: [PATCH] LibWebView+UI: Generate application context menus This migrates all duplicated context menus from the UIs to LibWebView. The context menu actions are now largely handled directly in LibWebView, with some UI-specific callbacks added to display e.g. confirmation dialogs. Actions that only ever apply to a specific web view are stored on the ViewImplementation itself. Actions that need to be dynamically applied to the active web view are stored on the Application. --- Libraries/LibWebView/Application.cpp | 28 ++ Libraries/LibWebView/Application.h | 14 + Libraries/LibWebView/Menu.h | 29 ++ Libraries/LibWebView/ViewImplementation.cpp | 269 +++++++++-- Libraries/LibWebView/ViewImplementation.h | 59 ++- Libraries/LibWebView/WebContentClient.cpp | 24 +- UI/AppKit/Application/ApplicationDelegate.mm | 33 +- UI/AppKit/Interface/LadybirdWebView.h | 9 - UI/AppKit/Interface/LadybirdWebView.mm | 470 +------------------ UI/AppKit/Interface/Menu.mm | 45 ++ UI/AppKit/Interface/Tab.mm | 8 - UI/AppKit/Interface/TabController.h | 5 - UI/AppKit/Interface/TabController.mm | 61 +-- UI/Qt/BrowserWindow.cpp | 100 +--- UI/Qt/BrowserWindow.h | 77 +-- UI/Qt/Menu.cpp | 82 +++- UI/Qt/Tab.cpp | 369 +-------------- UI/Qt/Tab.h | 43 +- 18 files changed, 553 insertions(+), 1172 deletions(-) diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index be549e9f5df..1cd7577e18d 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -268,6 +269,7 @@ ErrorOr Application::initialize(Main::Arguments const& arguments) }; create_platform_options(m_browser_options, m_web_content_options); + initialize_actions(); m_event_loop = create_platform_event_loop(); TRY(launch_services()); @@ -609,6 +611,32 @@ void Application::display_error_dialog(StringView error_message) const warnln("{}", error_message); } +void Application::initialize_actions() +{ + m_reload_action = Action::create("Reload"sv, ActionID::Reload, [this]() { + if (auto view = active_web_view(); view.has_value()) + view->reload(); + }); + + m_copy_selection_action = Action::create("Copy"sv, ActionID::CopySelection, [this]() { + if (auto view = active_web_view(); view.has_value()) + view->insert_text_into_clipboard(view->selected_text()); + }); + m_paste_action = Action::create("Paste"sv, ActionID::Paste, [this]() { + if (auto view = active_web_view(); view.has_value()) + view->paste_text_from_clipboard(); + }); + m_select_all_action = Action::create("Select All"sv, ActionID::SelectAll, [this]() { + if (auto view = active_web_view(); view.has_value()) + view->select_all(); + }); + + m_view_source_action = Action::create("View Source"sv, ActionID::ViewSource, [this]() { + if (auto view = active_web_view(); view.has_value()) + view->get_source(); + }); +} + ErrorOr Application::toggle_devtools_enabled() { if (m_devtools) { diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 9ad62a38dd4..afab10b375f 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -68,6 +68,12 @@ public: virtual void display_download_confirmation_dialog(StringView download_name, LexicalPath const& path) const; virtual void display_error_dialog(StringView error_message) const; + Action& reload_action() { return *m_reload_action; } + Action& copy_selection_action() { return *m_copy_selection_action; } + Action& paste_action() { return *m_paste_action; } + Action& select_all_action() { return *m_select_all_action; } + Action& view_source_action() { return *m_view_source_action; } + enum class DevtoolsState { Disabled, Enabled, @@ -97,6 +103,8 @@ private: ErrorOr launch_image_decoder_server(); ErrorOr launch_devtools_server(); + void initialize_actions(); + virtual Vector tab_list() const override; virtual Vector css_property_list() const override; virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override; @@ -152,6 +160,12 @@ private: OwnPtr m_event_loop; OwnPtr m_process_manager; + RefPtr m_reload_action; + RefPtr m_copy_selection_action; + RefPtr m_paste_action; + RefPtr m_select_all_action; + RefPtr m_view_source_action; + #if defined(AK_OS_MACOS) OwnPtr m_mach_port_server; #endif diff --git a/Libraries/LibWebView/Menu.h b/Libraries/LibWebView/Menu.h index 7c5da0c34b4..f4cc4475d4d 100644 --- a/Libraries/LibWebView/Menu.h +++ b/Libraries/LibWebView/Menu.h @@ -23,6 +23,35 @@ namespace WebView { enum class ActionID { + NavigateBack, + NavigateForward, + Reload, + + CopySelection, + Paste, + SelectAll, + + SearchSelectedText, + + TakeVisibleScreenshot, + TakeFullScreenshot, + + ViewSource, + + OpenInNewTab, + CopyURL, + + OpenImage, + CopyImage, + + OpenAudio, + OpenVideo, + PlayMedia, + PauseMedia, + MuteMedia, + UnmuteMedia, + ToggleMediaControlsState, + ToggleMediaLoopState, }; class WEBVIEW_API Action diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index 9c7af9df085..38b69982e9d 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -6,16 +6,20 @@ #include #include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include @@ -49,6 +53,8 @@ ViewImplementation::ViewImplementation() { s_all_views.set(m_view_id, this); + initialize_context_menus(); + m_repeated_crash_timer = Core::Timer::create_single_shot(1000, [this] { // Reset the "crashing a lot" counter after 1 second in case we just // happen to be visiting crashy websites a lot. @@ -271,11 +277,6 @@ void ViewImplementation::select_all() client().async_select_all(page_id()); } -void ViewImplementation::paste(String const& text) -{ - client().async_paste(page_id(), text); -} - void ViewImplementation::find_in_page(String const& query, CaseSensitivity case_sensitivity) { client().async_find_in_page(page_id(), query, case_sensitivity); @@ -456,31 +457,23 @@ void ViewImplementation::select_dropdown_closed(Optional const& selected_it client().async_select_dropdown_closed(page_id(), selected_item_id); } +void ViewImplementation::insert_text_into_clipboard(ByteString text) const +{ + if (on_insert_clipboard_entry) + on_insert_clipboard_entry({ move(text), "text/plain"_string }, {}); +} + +void ViewImplementation::paste_text_from_clipboard() +{ + if (on_request_clipboard_text) + client().async_paste(page_id(), on_request_clipboard_text()); +} + void ViewImplementation::retrieved_clipboard_entries(u64 request_id, ReadonlySpan items) { client().async_retrieved_clipboard_entries(page_id(), request_id, items); } -void ViewImplementation::toggle_media_play_state() -{ - client().async_toggle_media_play_state(page_id()); -} - -void ViewImplementation::toggle_media_mute_state() -{ - client().async_toggle_media_mute_state(page_id()); -} - -void ViewImplementation::toggle_media_loop_state() -{ - client().async_toggle_media_loop_state(page_id()); -} - -void ViewImplementation::toggle_media_controls_state() -{ - client().async_toggle_media_controls_state(page_id()); -} - void ViewImplementation::toggle_page_mute_state() { m_mute_state = Web::HTML::invert_mute_state(m_mute_state); @@ -513,8 +506,8 @@ void ViewImplementation::did_change_audio_play_state(Badge, We void ViewImplementation::did_update_navigation_buttons_state(Badge, bool back_enabled, bool forward_enabled) const { - if (on_navigation_buttons_state_changed) - on_navigation_buttons_state_changed(back_enabled, forward_enabled); + m_navigate_back_action->set_enabled(back_enabled); + m_navigate_forward_action->set_enabled(forward_enabled); } void ViewImplementation::did_allocate_backing_stores(Badge, i32 front_bitmap_id, Gfx::ShareableBitmap const& front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap const& back_bitmap) @@ -805,4 +798,226 @@ void ViewImplementation::use_native_user_style_sheet() set_user_style_sheet(native_stylesheet_source); } +void ViewImplementation::initialize_context_menus() +{ + auto& application = Application::the(); + + m_navigate_back_action = Action::create("Go Back"sv, ActionID::NavigateBack, [this]() { + traverse_the_history_by_delta(-1); + }); + m_navigate_forward_action = Action::create("Go Forward"sv, ActionID::NavigateForward, [this]() { + traverse_the_history_by_delta(+1); + }); + m_navigate_back_action->set_enabled(false); + m_navigate_forward_action->set_enabled(false); + + m_search_selected_text_action = Action::create("Search Selected Text"sv, ActionID::SearchSelectedText, [this]() { + auto const& search_engine = Application::settings().search_engine(); + if (!search_engine.has_value()) + return; + + auto url_string = search_engine->format_search_query_for_navigation(*m_search_text); + auto url = URL::Parser::basic_parse(url_string); + VERIFY(url.has_value()); + + if (on_link_click) + on_link_click(*url, "_blank"sv, 0); + }); + m_search_selected_text_action->set_visible(false); + + auto take_and_save_screenshot = [this](auto type) { + take_screenshot(type) + ->when_resolved([](auto const& path) { + Application::the().display_download_confirmation_dialog("Screenshot"sv, path); + }) + .when_rejected([](auto const& error) { + if (error.is_errno() && error.code() == ECANCELED) + return; + + auto error_message = MUST(String::formatted("{}", error)); + Application::the().display_error_dialog(error_message); + }); + }; + + m_take_visible_screenshot_action = Action::create("Take Visible Screenshot"sv, ActionID::TakeVisibleScreenshot, [take_and_save_screenshot]() { + take_and_save_screenshot(ScreenshotType::Visible); + }); + m_take_full_screenshot_action = Action::create("Take Full Screenshot"sv, ActionID::TakeFullScreenshot, [take_and_save_screenshot]() { + take_and_save_screenshot(ScreenshotType::Full); + }); + + m_open_in_new_tab_action = Action::create("Open in New Tab"sv, ActionID::OpenInNewTab, [this]() { + if (on_link_click) + on_link_click(m_context_menu_url, {}, Web::UIEvents::Mod_PlatformCtrl); + }); + m_copy_url_action = Action::create("Copy URL"sv, ActionID::CopyURL, [this]() { + insert_text_into_clipboard(url_text_to_copy(m_context_menu_url)); + }); + + m_open_image_action = Action::create("Open Image"sv, ActionID::OpenImage, [this]() { + if (on_link_click) + on_link_click(m_context_menu_url, {}, {}); + }); + m_copy_image_action = Action::create("Copy Image"sv, ActionID::CopyImage, [this]() { + if (!m_image_context_menu_bitmap.has_value()) + return; + + auto bitmap = m_image_context_menu_bitmap.release_value(); + if (!bitmap.is_valid()) + return; + + auto encoded = Gfx::PNGWriter::encode(*bitmap.bitmap()); + if (encoded.is_error()) + return; + + if (on_insert_clipboard_entry) + on_insert_clipboard_entry({ ByteString { encoded.value().bytes() }, "image/png"_string }, {}); + }); + + m_open_audio_action = Action::create("Open Audio"sv, ActionID::OpenAudio, [this]() { + if (on_link_click) + on_link_click(m_context_menu_url, {}, {}); + }); + m_open_video_action = Action::create("Open Video"sv, ActionID::OpenVideo, [this]() { + if (on_link_click) + on_link_click(m_context_menu_url, {}, {}); + }); + m_media_play_action = Action::create("Play"sv, ActionID::PlayMedia, [this]() { + client().async_toggle_media_play_state(page_id()); + }); + m_media_pause_action = Action::create("Pause"sv, ActionID::PauseMedia, [this]() { + client().async_toggle_media_play_state(page_id()); + }); + m_media_mute_action = Action::create("Mute"sv, ActionID::MuteMedia, [this]() { + client().async_toggle_media_mute_state(page_id()); + }); + m_media_unmute_action = Action::create("Unmute"sv, ActionID::UnmuteMedia, [this]() { + client().async_toggle_media_mute_state(page_id()); + }); + m_media_controls_action = Action::create_checkable("Show Controls"sv, ActionID::ToggleMediaControlsState, [this]() { + client().async_toggle_media_controls_state(page_id()); + }); + m_media_loop_action = Action::create_checkable("Loop"sv, ActionID::ToggleMediaLoopState, [this]() { + client().async_toggle_media_loop_state(page_id()); + }); + + m_page_context_menu = Menu::create("Page Context Menu"sv); + m_page_context_menu->add_action(*m_navigate_back_action); + m_page_context_menu->add_action(*m_navigate_forward_action); + m_page_context_menu->add_action(application.reload_action()); + m_page_context_menu->add_separator(); + m_page_context_menu->add_action(application.copy_selection_action()); + m_page_context_menu->add_action(application.paste_action()); + m_page_context_menu->add_action(application.select_all_action()); + m_page_context_menu->add_separator(); + m_page_context_menu->add_action(*m_search_selected_text_action); + m_page_context_menu->add_separator(); + m_page_context_menu->add_action(*m_take_visible_screenshot_action); + m_page_context_menu->add_action(*m_take_full_screenshot_action); + m_page_context_menu->add_separator(); + m_page_context_menu->add_action(application.view_source_action()); + + m_link_context_menu = Menu::create("Link Context Menu"sv); + m_link_context_menu->add_action(*m_open_in_new_tab_action); + m_link_context_menu->add_action(*m_copy_url_action); + + m_image_context_menu = Menu::create("Image Context Menu"sv); + m_image_context_menu->add_action(*m_open_image_action); + m_image_context_menu->add_action(*m_open_in_new_tab_action); + m_image_context_menu->add_separator(); + m_image_context_menu->add_action(*m_copy_image_action); + m_image_context_menu->add_action(*m_copy_url_action); + + m_media_context_menu = Menu::create("Media Context Menu"sv); + m_media_context_menu->add_action(*m_media_play_action); + m_media_context_menu->add_action(*m_media_pause_action); + m_media_context_menu->add_action(*m_media_mute_action); + m_media_context_menu->add_action(*m_media_unmute_action); + m_media_context_menu->add_action(*m_media_controls_action); + m_media_context_menu->add_action(*m_media_loop_action); + m_media_context_menu->add_separator(); + m_media_context_menu->add_action(*m_open_audio_action); + m_media_context_menu->add_action(*m_open_video_action); + m_media_context_menu->add_action(*m_open_in_new_tab_action); + m_media_context_menu->add_separator(); + m_media_context_menu->add_action(*m_copy_url_action); +} + +void ViewImplementation::did_request_page_context_menu(Badge, Gfx::IntPoint content_position) +{ + auto const& search_engine = Application::settings().search_engine(); + + auto selected_text = search_engine.has_value() ? selected_text_with_whitespace_collapsed() : OptionalNone {}; + TemporaryChange change_url { m_search_text, move(selected_text) }; + + if (m_search_text.has_value()) { + m_search_selected_text_action->set_text(search_engine->format_search_query_for_display(*m_search_text)); + m_search_selected_text_action->set_visible(true); + } else { + m_search_selected_text_action->set_visible(false); + } + + if (m_page_context_menu->on_activation) + m_page_context_menu->on_activation(to_widget_position(content_position)); +} + +void ViewImplementation::did_request_link_context_menu(Badge, Gfx::IntPoint content_position, URL::URL url) +{ + m_context_menu_url = move(url); + + m_open_in_new_tab_action->set_text("Open in New Tab"sv); + + switch (url_type(m_context_menu_url)) { + case URLType::Email: + m_copy_url_action->set_text("Copy Email Address"sv); + break; + case URLType::Telephone: + m_copy_url_action->set_text("Copy Phone Number"sv); + break; + case URLType::Other: + m_copy_url_action->set_text("Copy Link Address"sv); + break; + } + + if (m_link_context_menu->on_activation) + m_link_context_menu->on_activation(to_widget_position(content_position)); +} + +void ViewImplementation::did_request_image_context_menu(Badge, Gfx::IntPoint content_position, URL::URL url, Optional bitmap) +{ + m_context_menu_url = move(url); + m_image_context_menu_bitmap = move(bitmap); + + m_open_in_new_tab_action->set_text("Open Image in New Tab"sv); + m_copy_url_action->set_text("Copy Image URL"sv); + + m_copy_image_action->set_enabled(m_image_context_menu_bitmap.has_value()); + + if (m_image_context_menu->on_activation) + m_image_context_menu->on_activation(to_widget_position(content_position)); +} + +void ViewImplementation::did_request_media_context_menu(Badge, Gfx::IntPoint content_position, Web::Page::MediaContextMenu menu) +{ + m_context_menu_url = move(menu.media_url); + + m_open_in_new_tab_action->set_text(menu.is_video ? "Open Video in New Tab"sv : "Open Audio in new Tab"sv); + m_copy_url_action->set_text(menu.is_video ? "Copy Video URL"sv : "Copy Audio URL"sv); + + m_open_audio_action->set_visible(!menu.is_video); + m_open_video_action->set_visible(menu.is_video); + + m_media_play_action->set_visible(!menu.is_playing); + m_media_pause_action->set_visible(menu.is_playing); + + m_media_mute_action->set_visible(!menu.is_muted); + m_media_unmute_action->set_visible(menu.is_muted); + + m_media_controls_action->set_checked(menu.has_user_agent_controls); + m_media_loop_action->set_checked(menu.is_looping); + + if (m_media_context_menu->on_activation) + m_media_context_menu->on_activation(to_widget_position(content_position)); +} + } diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index ed2c84ac920..d3a7def47cb 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -88,7 +88,6 @@ public: void find_in_page(String const& query, CaseSensitivity = CaseSensitivity::CaseInsensitive); void find_in_page_next_match(); void find_in_page_previous_match(); - void paste(String const&); void get_source(); @@ -132,13 +131,10 @@ public: void file_picker_closed(Vector selected_files); void select_dropdown_closed(Optional const& selected_item_id); + void insert_text_into_clipboard(ByteString) const; + void paste_text_from_clipboard(); void retrieved_clipboard_entries(u64 request_id, ReadonlySpan); - void toggle_media_play_state(); - void toggle_media_mute_state(); - void toggle_media_loop_state(); - void toggle_media_controls_state(); - Web::HTML::MuteState page_mute_state() const { return m_mute_state; } void toggle_page_mute_state(); @@ -174,10 +170,6 @@ public: Function)> on_new_web_view; Function on_activate_tab; Function on_close; - Function on_context_menu_request; - Function on_link_context_menu_request; - Function const&)> on_image_context_menu_request; - Function on_media_context_menu_request; Function on_link_hover; Function on_link_unhover; Function on_link_click; @@ -234,9 +226,21 @@ public: Function on_request_clipboard_text; Function on_request_clipboard_entries; Function on_audio_play_state_changed; - Function on_navigation_buttons_state_changed; Function on_web_content_crashed; + Menu& page_context_menu() { return *m_page_context_menu; } + Menu& link_context_menu() { return *m_link_context_menu; } + Menu& image_context_menu() { return *m_image_context_menu; } + Menu& media_context_menu() { return *m_media_context_menu; } + + void did_request_page_context_menu(Badge, Gfx::IntPoint content_position); + void did_request_link_context_menu(Badge, Gfx::IntPoint content_position, URL::URL url); + void did_request_image_context_menu(Badge, Gfx::IntPoint content_position, URL::URL url, Optional bitmap); + void did_request_media_context_menu(Badge, Gfx::IntPoint content_position, Web::Page::MediaContextMenu menu); + + Action& navigate_back_action() { return *m_navigate_back_action; } + Action& navigate_forward_action() { return *m_navigate_forward_action; } + virtual Web::DevicePixelSize viewport_size() const = 0; virtual Gfx::IntPoint to_content_position(Gfx::IntPoint widget_position) const = 0; virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0; @@ -272,6 +276,8 @@ protected: virtual void autoplay_settings_changed() override; virtual void do_not_track_changed() override; + void initialize_context_menus(); + struct SharedBitmap { i32 id { -1 }; Web::DevicePixelSize last_painted_size; @@ -294,6 +300,37 @@ protected: float m_device_pixel_ratio { 1.0 }; double m_maximum_frames_per_second { 60.0 }; + RefPtr m_page_context_menu; + RefPtr m_link_context_menu; + RefPtr m_image_context_menu; + RefPtr m_media_context_menu; + + RefPtr m_navigate_back_action; + RefPtr m_navigate_forward_action; + + RefPtr m_search_selected_text_action; + Optional m_search_text; + + RefPtr m_take_visible_screenshot_action; + RefPtr m_take_full_screenshot_action; + + RefPtr m_open_in_new_tab_action; + RefPtr m_copy_url_action; + URL::URL m_context_menu_url; + + RefPtr m_open_image_action; + RefPtr m_copy_image_action; + Optional m_image_context_menu_bitmap; + + RefPtr m_open_audio_action; + RefPtr m_open_video_action; + RefPtr m_media_play_action; + RefPtr m_media_pause_action; + RefPtr m_media_mute_action; + RefPtr m_media_unmute_action; + RefPtr m_media_controls_action; + RefPtr m_media_loop_action; + Queue m_pending_input_events; RefPtr m_backing_store_shrink_timer; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 4700d581f4a..0cb0e237ed6 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -262,34 +262,26 @@ void WebContentClient::did_middle_click_link(u64 page_id, URL::URL url, ByteStri void WebContentClient::did_request_context_menu(u64 page_id, Gfx::IntPoint content_position) { - if (auto view = view_for_page_id(page_id); view.has_value()) { - if (view->on_context_menu_request) - view->on_context_menu_request(view->to_widget_position(content_position)); - } + if (auto view = view_for_page_id(page_id); view.has_value()) + view->did_request_page_context_menu({}, content_position); } void WebContentClient::did_request_link_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned) { - if (auto view = view_for_page_id(page_id); view.has_value()) { - if (view->on_link_context_menu_request) - view->on_link_context_menu_request(url, view->to_widget_position(content_position)); - } + if (auto view = view_for_page_id(page_id); view.has_value()) + view->did_request_link_context_menu({}, content_position, move(url)); } void WebContentClient::did_request_image_context_menu(u64 page_id, Gfx::IntPoint content_position, URL::URL url, ByteString, unsigned, Optional bitmap) { - if (auto view = view_for_page_id(page_id); view.has_value()) { - if (view->on_image_context_menu_request) - view->on_image_context_menu_request(url, view->to_widget_position(content_position), bitmap); - } + if (auto view = view_for_page_id(page_id); view.has_value()) + view->did_request_image_context_menu({}, content_position, move(url), move(bitmap)); } void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint content_position, ByteString, unsigned, Web::Page::MediaContextMenu menu) { - if (auto view = view_for_page_id(page_id); view.has_value()) { - if (view->on_media_context_menu_request) - view->on_media_context_menu_request(view->to_widget_position(content_position), menu); - } + if (auto view = view_for_page_id(page_id); view.has_value()) + view->did_request_media_context_menu({}, content_position, move(menu)); } void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_url, String source) diff --git a/UI/AppKit/Application/ApplicationDelegate.mm b/UI/AppKit/Application/ApplicationDelegate.mm index 78b05216869..0f1bc2f700c 100644 --- a/UI/AppKit/Application/ApplicationDelegate.mm +++ b/UI/AppKit/Application/ApplicationDelegate.mm @@ -10,10 +10,10 @@ #import #import #import +#import #import #import #import - #import #if !__has_feature(objc_arc) @@ -471,17 +471,12 @@ [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy" - action:@selector(copy:) - keyEquivalent:@"c"]]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste" - action:@selector(paste:) - keyEquivalent:@"v"]]; + + [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().copy_selection_action())]; + [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().paste_action())]; [submenu addItem:[NSMenuItem separatorItem]]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All" - action:@selector(selectAll:) - keyEquivalent:@"a"]]; + [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().select_all_action())]; [submenu addItem:[NSMenuItem separatorItem]]; [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Find..." @@ -586,19 +581,11 @@ - (NSMenuItem*)createHistoryMenu { auto* menu = [[NSMenuItem alloc] init]; + auto* submenu = [[NSMenu alloc] initWithTitle:@"History"]; + [submenu setAutoenablesItems:NO]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload Page" - action:@selector(reload:) - keyEquivalent:@"r"]]; - [submenu addItem:[NSMenuItem separatorItem]]; - - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Back" - action:@selector(navigateBack:) - keyEquivalent:@"["]]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Navigate Forward" - action:@selector(navigateForward:) - keyEquivalent:@"]"]]; + [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().reload_action())]; [submenu addItem:[NSMenuItem separatorItem]]; [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"Clear History" @@ -614,9 +601,7 @@ auto* menu = [[NSMenuItem alloc] init]; auto* submenu = [[NSMenu alloc] initWithTitle:@"Inspect"]; - [submenu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source" - action:@selector(viewSource:) - keyEquivalent:@"u"]]; + [submenu addItem:Ladybird::create_application_menu_item(WebView::Application::the().view_source_action())]; self.toggle_devtools_menu_item = [[NSMenuItem alloc] initWithTitle:@"Enable DevTools" action:@selector(toggleDevToolsEnabled:) diff --git a/UI/AppKit/Interface/LadybirdWebView.h b/UI/AppKit/Interface/LadybirdWebView.h index d7e32e0487f..01e757bcf97 100644 --- a/UI/AppKit/Interface/LadybirdWebView.h +++ b/UI/AppKit/Interface/LadybirdWebView.h @@ -36,9 +36,6 @@ - (void)onLoadFinish:(URL::URL const&)url; - (void)onURLChange:(URL::URL const&)url; -- (void)onBackNavigationEnabled:(BOOL)back_enabled - forwardNavigationEnabled:(BOOL)forward_enabled; - - (void)onTitleChange:(Utf16String const&)title; - (void)onFaviconChange:(Gfx::Bitmap const&)bitmap; - (void)onAudioPlayStateChange:(Web::HTML::AudioPlayState)play_state; @@ -58,10 +55,6 @@ - (void)loadURL:(URL::URL const&)url; - (void)loadHTML:(StringView)html; -- (void)navigateBack; -- (void)navigateForward; -- (void)reload; - - (WebView::ViewImplementation&)view; - (String const&)handle; @@ -89,6 +82,4 @@ - (void)debugRequest:(ByteString const&)request argument:(ByteString const&)argument; -- (void)viewSource; - @end diff --git a/UI/AppKit/Interface/LadybirdWebView.mm b/UI/AppKit/Interface/LadybirdWebView.mm index 13f3ab458cb..30b090c3286 100644 --- a/UI/AppKit/Interface/LadybirdWebView.mm +++ b/UI/AppKit/Interface/LadybirdWebView.mm @@ -5,22 +5,15 @@ */ #include -#include #include -#include -#include -#include #include #include -#include -#include #include -#include -#import #import #import #import +#import #import #import @@ -28,14 +21,6 @@ # error "This project requires ARC" #endif -static constexpr NSInteger CONTEXT_MENU_PLAY_PAUSE_TAG = 1; -static constexpr NSInteger CONTEXT_MENU_MUTE_UNMUTE_TAG = 2; -static constexpr NSInteger CONTEXT_MENU_CONTROLS_TAG = 3; -static constexpr NSInteger CONTEXT_MENU_LOOP_TAG = 4; -static constexpr NSInteger CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG = 5; -static constexpr NSInteger CONTEXT_MENU_COPY_LINK_TAG = 6; -static constexpr NSInteger CONTEXT_MENU_COPY_IMAGE_TAG = 7; - // Calls to [NSCursor hide] and [NSCursor unhide] must be balanced. We use this struct to ensure // we only call [NSCursor hide] once and to ensure that we do call [NSCursor unhide]. // https://developer.apple.com/documentation/appkit/nscursor#1651301 @@ -55,10 +40,6 @@ struct HideCursor { { OwnPtr m_web_view_bridge; - URL::URL m_context_menu_url; - Optional m_context_menu_bitmap; - Optional m_context_menu_search_text; - Optional m_hidden_cursor; // We have to send key events for modifer keys, but AppKit does not generate key down/up events when only a modifier @@ -71,8 +52,7 @@ struct HideCursor { @property (nonatomic, strong) NSMenu* page_context_menu; @property (nonatomic, strong) NSMenu* link_context_menu; @property (nonatomic, strong) NSMenu* image_context_menu; -@property (nonatomic, strong) NSMenu* audio_context_menu; -@property (nonatomic, strong) NSMenu* video_context_menu; +@property (nonatomic, strong) NSMenu* media_context_menu; @property (nonatomic, strong) NSMenu* select_dropdown; @property (nonatomic, strong) NSTextField* status_label; @property (nonatomic, strong) NSAlert* dialog; @@ -86,11 +66,6 @@ struct HideCursor { @implementation LadybirdWebView -@synthesize page_context_menu = _page_context_menu; -@synthesize link_context_menu = _link_context_menu; -@synthesize image_context_menu = _image_context_menu; -@synthesize audio_context_menu = _audio_context_menu; -@synthesize video_context_menu = _video_context_menu; @synthesize status_label = _status_label; - (instancetype)init:(id)observer @@ -136,6 +111,11 @@ struct HideCursor { m_web_view_bridge = MUST(Ladybird::WebViewBridge::create(move(screen_rects), device_pixel_ratio, maximum_frames_per_second, [delegate preferredColorScheme], [delegate preferredContrast], [delegate preferredMotion])); [self setWebViewCallbacks]; + self.page_context_menu = Ladybird::create_context_menu(self, [self view].page_context_menu()); + self.link_context_menu = Ladybird::create_context_menu(self, [self view].link_context_menu()); + self.image_context_menu = Ladybird::create_context_menu(self, [self view].image_context_menu()); + self.media_context_menu = Ladybird::create_context_menu(self, [self view].media_context_menu()); + auto* area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved owner:self @@ -162,21 +142,6 @@ struct HideCursor { m_web_view_bridge->load_html(html); } -- (void)navigateBack -{ - m_web_view_bridge->traverse_the_history_by_delta(-1); -} - -- (void)navigateForward -{ - m_web_view_bridge->traverse_the_history_by_delta(1); -} - -- (void)reload -{ - m_web_view_bridge->reload(); -} - - (WebView::ViewImplementation&)view { return *m_web_view_bridge; @@ -281,11 +246,6 @@ struct HideCursor { m_web_view_bridge->debug_request(request, argument); } -- (void)viewSource -{ - m_web_view_bridge->get_source(); -} - #pragma mark - Private methods static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_type) @@ -391,15 +351,6 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ [self.observer onURLChange:url]; }; - m_web_view_bridge->on_navigation_buttons_state_changed = [weak_self](auto back_enabled, auto forward_enabled) { - LadybirdWebView* self = weak_self; - if (self == nil) { - return; - } - [self.observer onBackNavigationEnabled:back_enabled - forwardNavigationEnabled:forward_enabled]; - }; - m_web_view_bridge->on_title_change = [weak_self](auto const& title) { LadybirdWebView* self = weak_self; if (self == nil) { @@ -622,107 +573,6 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ [self.observer onCreateNewTab:url activateTab:Web::HTML::ActivateTab::No]; }; - m_web_view_bridge->on_context_menu_request = [weak_self](auto position) { - LadybirdWebView* self = weak_self; - if (self == nil) { - return; - } - auto* search_selected_text_menu_item = [self.page_context_menu itemWithTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG]; - - auto const& search_engine = WebView::Application::settings().search_engine(); - - auto selected_text = self.observer && search_engine.has_value() - ? m_web_view_bridge->selected_text_with_whitespace_collapsed() - : OptionalNone {}; - TemporaryChange change_url { m_context_menu_search_text, move(selected_text) }; - - if (m_context_menu_search_text.has_value()) { - auto action_text = search_engine->format_search_query_for_display(*m_context_menu_search_text); - [search_selected_text_menu_item setTitle:Ladybird::string_to_ns_string(action_text)]; - [search_selected_text_menu_item setHidden:NO]; - } else { - [search_selected_text_menu_item setHidden:YES]; - } - - auto* event = Ladybird::create_context_menu_mouse_event(self, position); - [NSMenu popUpContextMenu:self.page_context_menu withEvent:event forView:self]; - }; - - m_web_view_bridge->on_link_context_menu_request = [weak_self](auto const& url, auto position) { - LadybirdWebView* self = weak_self; - if (self == nil) { - return; - } - TemporaryChange change_url { m_context_menu_url, url }; - - auto* copy_link_menu_item = [self.link_context_menu itemWithTag:CONTEXT_MENU_COPY_LINK_TAG]; - - switch (WebView::url_type(url)) { - case WebView::URLType::Email: - [copy_link_menu_item setTitle:@"Copy Email Address"]; - break; - case WebView::URLType::Telephone: - [copy_link_menu_item setTitle:@"Copy Phone Number"]; - break; - case WebView::URLType::Other: - [copy_link_menu_item setTitle:@"Copy URL"]; - break; - } - - auto* event = Ladybird::create_context_menu_mouse_event(self, position); - [NSMenu popUpContextMenu:self.link_context_menu withEvent:event forView:self]; - }; - - m_web_view_bridge->on_image_context_menu_request = [weak_self](auto const& url, auto position, auto const& bitmap) { - LadybirdWebView* self = weak_self; - if (self == nil) { - return; - } - TemporaryChange change_url { m_context_menu_url, url }; - TemporaryChange change_bitmap { m_context_menu_bitmap, bitmap }; - - auto* copy_image_menu_item = [self.image_context_menu itemWithTag:CONTEXT_MENU_COPY_IMAGE_TAG]; - [copy_image_menu_item setEnabled:bitmap.has_value()]; - - auto* event = Ladybird::create_context_menu_mouse_event(self, position); - [NSMenu popUpContextMenu:self.image_context_menu withEvent:event forView:self]; - }; - - m_web_view_bridge->on_media_context_menu_request = [weak_self](auto position, auto const& menu) { - LadybirdWebView* self = weak_self; - if (self == nil) { - return; - } - TemporaryChange change_url { m_context_menu_url, menu.media_url }; - - auto* context_menu = menu.is_video ? self.video_context_menu : self.audio_context_menu; - auto* play_pause_menu_item = [context_menu itemWithTag:CONTEXT_MENU_PLAY_PAUSE_TAG]; - auto* mute_unmute_menu_item = [context_menu itemWithTag:CONTEXT_MENU_MUTE_UNMUTE_TAG]; - auto* controls_menu_item = [context_menu itemWithTag:CONTEXT_MENU_CONTROLS_TAG]; - auto* loop_menu_item = [context_menu itemWithTag:CONTEXT_MENU_LOOP_TAG]; - - if (menu.is_playing) { - [play_pause_menu_item setTitle:@"Pause"]; - } else { - [play_pause_menu_item setTitle:@"Play"]; - } - - if (menu.is_muted) { - [mute_unmute_menu_item setTitle:@"Unmute"]; - } else { - [mute_unmute_menu_item setTitle:@"Mute"]; - } - - auto controls_state = menu.has_user_agent_controls ? NSControlStateValueOn : NSControlStateValueOff; - [controls_menu_item setState:controls_state]; - - auto loop_state = menu.is_looping ? NSControlStateValueOn : NSControlStateValueOff; - [loop_menu_item setState:loop_state]; - - auto* event = Ladybird::create_context_menu_mouse_event(self, position); - [NSMenu popUpContextMenu:context_menu withEvent:event forView:self]; - }; - m_web_view_bridge->on_request_alert = [weak_self](auto const& message) { LadybirdWebView* self = weak_self; if (self == nil) { @@ -1155,314 +1005,8 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ m_web_view_bridge->color_picker_update(Ladybird::ns_color_to_gfx_color([NSColorPanel sharedColorPanel].color), Web::HTML::ColorPickerUpdateState::Closed); } -- (void)copy:(id)sender -{ - copy_data_to_clipboard(m_web_view_bridge->selected_text(), NSPasteboardTypeString); -} - -- (void)paste:(id)sender -{ - if (m_web_view_bridge->on_request_clipboard_text) - m_web_view_bridge->paste(m_web_view_bridge->on_request_clipboard_text()); -} - -- (void)selectAll:(id)sender -{ - m_web_view_bridge->select_all(); -} - -- (void)searchSelectedText:(id)sender -{ - auto const& search_engine = WebView::Application::settings().search_engine(); - if (!search_engine.has_value()) - return; - - auto url_string = search_engine->format_search_query_for_navigation(*m_context_menu_search_text); - auto url = URL::Parser::basic_parse(url_string); - VERIFY(url.has_value()); - [self.observer onCreateNewTab:url.release_value() activateTab:Web::HTML::ActivateTab::Yes]; -} - -- (void)takeVisibleScreenshot:(id)sender -{ - [self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Visible]; -} - -- (void)takeFullScreenshot:(id)sender -{ - [self takeScreenshot:WebView::ViewImplementation::ScreenshotType::Full]; -} - -- (void)takeScreenshot:(WebView::ViewImplementation::ScreenshotType)type -{ - m_web_view_bridge->take_screenshot(type) - ->when_resolved([](auto const& path) { - WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path); - }) - .when_rejected([](auto const& error) { - if (error.is_errno() && error.code() == ECANCELED) - return; - - auto error_message = MUST(String::formatted("{}", error)); - WebView::Application::the().display_error_dialog(error_message); - }); -} - -- (void)openLink:(id)sender -{ - m_web_view_bridge->on_link_click(m_context_menu_url, {}, 0); -} - -- (void)openLinkInNewTab:(id)sender -{ - m_web_view_bridge->on_link_middle_click(m_context_menu_url, {}, 0); -} - -- (void)copyLink:(id)sender -{ - auto link = WebView::url_text_to_copy(m_context_menu_url); - copy_data_to_clipboard(link, NSPasteboardTypeString); -} - -- (void)copyImage:(id)sender -{ - if (!m_context_menu_bitmap.has_value()) { - return; - } - auto* bitmap = m_context_menu_bitmap.value().bitmap(); - if (bitmap == nullptr) { - return; - } - - auto png = Gfx::PNGWriter::encode(*bitmap); - if (png.is_error()) { - return; - } - - auto* data = [NSData dataWithBytes:png.value().data() length:png.value().size()]; - - auto* pasteBoard = [NSPasteboard generalPasteboard]; - [pasteBoard clearContents]; - [pasteBoard setData:data forType:NSPasteboardTypePNG]; -} - -- (void)toggleMediaPlayState:(id)sender -{ - m_web_view_bridge->toggle_media_play_state(); -} - -- (void)toggleMediaMuteState:(id)sender -{ - m_web_view_bridge->toggle_media_mute_state(); -} - -- (void)toggleMediaControlsState:(id)sender -{ - m_web_view_bridge->toggle_media_controls_state(); -} - -- (void)toggleMediaLoopState:(id)sender -{ - m_web_view_bridge->toggle_media_loop_state(); -} - #pragma mark - Properties -- (NSMenu*)page_context_menu -{ - if (!_page_context_menu) { - _page_context_menu = [[NSMenu alloc] initWithTitle:@"Page Context Menu"]; - - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Back" - action:@selector(navigateBack:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Go Forward" - action:@selector(navigateForward:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Reload" - action:@selector(reload:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[NSMenuItem separatorItem]]; - - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy" - action:@selector(copy:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Paste" - action:@selector(paste:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Select All" - action:@selector(selectAll:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[NSMenuItem separatorItem]]; - - auto* search_selected_text_menu_item = [[NSMenuItem alloc] initWithTitle:@"Search for " - action:@selector(searchSelectedText:) - keyEquivalent:@""]; - [search_selected_text_menu_item setTag:CONTEXT_MENU_SEARCH_SELECTED_TEXT_TAG]; - [_page_context_menu addItem:search_selected_text_menu_item]; - [_page_context_menu addItem:[NSMenuItem separatorItem]]; - - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Visible Screenshot" - action:@selector(takeVisibleScreenshot:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Take Full Screenshot" - action:@selector(takeFullScreenshot:) - keyEquivalent:@""]]; - [_page_context_menu addItem:[NSMenuItem separatorItem]]; - - [_page_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"View Source" - action:@selector(viewSource:) - keyEquivalent:@""]]; - } - - return _page_context_menu; -} - -- (NSMenu*)link_context_menu -{ - if (!_link_context_menu) { - _link_context_menu = [[NSMenu alloc] initWithTitle:@"Link Context Menu"]; - - [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open" - action:@selector(openLink:) - keyEquivalent:@""]]; - [_link_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open in New Tab" - action:@selector(openLinkInNewTab:) - keyEquivalent:@""]]; - [_link_context_menu addItem:[NSMenuItem separatorItem]]; - - auto* copy_link_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy URL" - action:@selector(copyLink:) - keyEquivalent:@""]; - [copy_link_menu_item setTag:CONTEXT_MENU_COPY_LINK_TAG]; - [_link_context_menu addItem:copy_link_menu_item]; - } - - return _link_context_menu; -} - -- (NSMenu*)image_context_menu -{ - if (!_image_context_menu) { - _image_context_menu = [[NSMenu alloc] initWithTitle:@"Image Context Menu"]; - [_image_context_menu setAutoenablesItems:NO]; - - [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image" - action:@selector(openLink:) - keyEquivalent:@""]]; - [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Image in New Tab" - action:@selector(openLinkInNewTab:) - keyEquivalent:@""]]; - [_image_context_menu addItem:[NSMenuItem separatorItem]]; - - auto* copy_image_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy Image" - action:@selector(copyImage:) - keyEquivalent:@""]; - [copy_image_menu_item setTag:CONTEXT_MENU_COPY_IMAGE_TAG]; - [_image_context_menu addItem:copy_image_menu_item]; - - [_image_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Image URL" - action:@selector(copyLink:) - keyEquivalent:@""]]; - } - - return _image_context_menu; -} - -- (NSMenu*)audio_context_menu -{ - if (!_audio_context_menu) { - _audio_context_menu = [[NSMenu alloc] initWithTitle:@"Audio Context Menu"]; - - auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play" - action:@selector(toggleMediaPlayState:) - keyEquivalent:@""]; - [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG]; - - auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute" - action:@selector(toggleMediaMuteState:) - keyEquivalent:@""]; - [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG]; - - auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls" - action:@selector(toggleMediaControlsState:) - keyEquivalent:@""]; - [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG]; - - auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop" - action:@selector(toggleMediaLoopState:) - keyEquivalent:@""]; - [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG]; - - [_audio_context_menu addItem:play_pause_menu_item]; - [_audio_context_menu addItem:mute_unmute_menu_item]; - [_audio_context_menu addItem:controls_menu_item]; - [_audio_context_menu addItem:loop_menu_item]; - [_audio_context_menu addItem:[NSMenuItem separatorItem]]; - - [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio" - action:@selector(openLink:) - keyEquivalent:@""]]; - [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Audio in New Tab" - action:@selector(openLinkInNewTab:) - keyEquivalent:@""]]; - [_audio_context_menu addItem:[NSMenuItem separatorItem]]; - - [_audio_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Audio URL" - action:@selector(copyLink:) - keyEquivalent:@""]]; - } - - return _audio_context_menu; -} - -- (NSMenu*)video_context_menu -{ - if (!_video_context_menu) { - _video_context_menu = [[NSMenu alloc] initWithTitle:@"Video Context Menu"]; - - auto* play_pause_menu_item = [[NSMenuItem alloc] initWithTitle:@"Play" - action:@selector(toggleMediaPlayState:) - keyEquivalent:@""]; - [play_pause_menu_item setTag:CONTEXT_MENU_PLAY_PAUSE_TAG]; - - auto* mute_unmute_menu_item = [[NSMenuItem alloc] initWithTitle:@"Mute" - action:@selector(toggleMediaMuteState:) - keyEquivalent:@""]; - [mute_unmute_menu_item setTag:CONTEXT_MENU_MUTE_UNMUTE_TAG]; - - auto* controls_menu_item = [[NSMenuItem alloc] initWithTitle:@"Controls" - action:@selector(toggleMediaControlsState:) - keyEquivalent:@""]; - [controls_menu_item setTag:CONTEXT_MENU_CONTROLS_TAG]; - - auto* loop_menu_item = [[NSMenuItem alloc] initWithTitle:@"Loop" - action:@selector(toggleMediaLoopState:) - keyEquivalent:@""]; - [loop_menu_item setTag:CONTEXT_MENU_LOOP_TAG]; - - [_video_context_menu addItem:play_pause_menu_item]; - [_video_context_menu addItem:mute_unmute_menu_item]; - [_video_context_menu addItem:controls_menu_item]; - [_video_context_menu addItem:loop_menu_item]; - [_video_context_menu addItem:[NSMenuItem separatorItem]]; - - [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video" - action:@selector(openLink:) - keyEquivalent:@""]]; - [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Open Video in New Tab" - action:@selector(openLinkInNewTab:) - keyEquivalent:@""]]; - [_video_context_menu addItem:[NSMenuItem separatorItem]]; - - [_video_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Copy Video URL" - action:@selector(copyLink:) - keyEquivalent:@""]]; - } - - return _video_context_menu; -} - - (NSTextField*)status_label { if (!_status_label) { diff --git a/UI/AppKit/Interface/Menu.mm b/UI/AppKit/Interface/Menu.mm index d8f8cdbbe55..e7017c8d814 100644 --- a/UI/AppKit/Interface/Menu.mm +++ b/UI/AppKit/Interface/Menu.mm @@ -38,6 +38,22 @@ if (!action) return; + if (![[[NSApp keyWindow] firstResponder] isKindOfClass:[LadybirdWebView class]]) { + switch (action->id()) { + case WebView::ActionID::CopySelection: + [NSApp sendAction:@selector(copy:) to:nil from:sender]; + return; + case WebView::ActionID::Paste: + [NSApp sendAction:@selector(paste:) to:nil from:sender]; + return; + case WebView::ActionID::SelectAll: + [NSApp sendAction:@selector(selectAll:) to:nil from:sender]; + return; + default: + break; + } + } + if (action->is_checkable()) action->set_checked(!action->checked()); action->activate(); @@ -94,6 +110,35 @@ private: static void initialize_native_control(WebView::Action& action, id control) { + switch (action.id()) { + case WebView::ActionID::NavigateBack: + [control setKeyEquivalent:@"["]; + break; + case WebView::ActionID::NavigateForward: + [control setKeyEquivalent:@"]"]; + break; + case WebView::ActionID::Reload: + [control setKeyEquivalent:@"r"]; + break; + + case WebView::ActionID::CopySelection: + [control setKeyEquivalent:@"c"]; + break; + case WebView::ActionID::Paste: + [control setKeyEquivalent:@"v"]; + break; + case WebView::ActionID::SelectAll: + [control setKeyEquivalent:@"a"]; + break; + + case WebView::ActionID::ViewSource: + [control setKeyEquivalent:@"u"]; + break; + + default: + break; + } + action.add_observer(ActionObserver::create(action, control)); } diff --git a/UI/AppKit/Interface/Tab.mm b/UI/AppKit/Interface/Tab.mm index c04920fe209..9661437b4f6 100644 --- a/UI/AppKit/Interface/Tab.mm +++ b/UI/AppKit/Interface/Tab.mm @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -285,13 +284,6 @@ static constexpr CGFloat const WINDOW_HEIGHT = 800; [[self tabController] onURLChange:url]; } -- (void)onBackNavigationEnabled:(BOOL)back_enabled - forwardNavigationEnabled:(BOOL)forward_enabled -{ - [[self tabController] onBackNavigationEnabled:back_enabled - forwardNavigationEnabled:forward_enabled]; -} - - (void)onTitleChange:(Utf16String const&)title { self.title = Ladybird::utf16_string_to_ns_string(title); diff --git a/UI/AppKit/Interface/TabController.h b/UI/AppKit/Interface/TabController.h index 2b06776822f..71c2579ca9d 100644 --- a/UI/AppKit/Interface/TabController.h +++ b/UI/AppKit/Interface/TabController.h @@ -33,14 +33,9 @@ struct TabSettings { - (void)onLoadStart:(URL::URL const&)url isRedirect:(BOOL)isRedirect; - (void)onURLChange:(URL::URL const&)url; -- (void)onBackNavigationEnabled:(BOOL)back_enabled - forwardNavigationEnabled:(BOOL)forward_enabled; - (void)onCreateNewTab; -- (void)navigateBack:(id)sender; -- (void)navigateForward:(id)sender; -- (void)reload:(id)sender; - (void)clearHistory; - (void)setPopupBlocking:(BOOL)block_popups; diff --git a/UI/AppKit/Interface/TabController.mm b/UI/AppKit/Interface/TabController.mm index ba51fc1821f..5d7dc41451e 100644 --- a/UI/AppKit/Interface/TabController.mm +++ b/UI/AppKit/Interface/TabController.mm @@ -7,15 +7,14 @@ #include #include #include -#include #include #include #include #import #import -#import #import +#import #import #import #import @@ -58,9 +57,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde TabSettings m_settings; OwnPtr m_autocomplete; - - bool m_can_navigate_back; - bool m_can_navigate_forward; } @property (nonatomic, strong) Tab* parent; @@ -128,9 +124,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde [self.autocomplete showWithSuggestions:move(suggestions)]; }; - - m_can_navigate_back = false; - m_can_navigate_forward = false; } return self; @@ -170,14 +163,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde [self.window makeFirstResponder:[self tab].web_view]; } -- (void)onBackNavigationEnabled:(BOOL)back_enabled - forwardNavigationEnabled:(BOOL)forward_enabled -{ - m_can_navigate_back = back_enabled; - m_can_navigate_forward = forward_enabled; - [self updateNavigationButtonStates]; -} - - (void)onCreateNewTab { [self setPopupBlocking:m_settings.block_popups]; @@ -202,21 +187,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde [self updateZoomButton]; } -- (void)navigateBack:(id)sender -{ - [[[self tab] web_view] navigateBack]; -} - -- (void)navigateForward:(id)sender -{ - [[[self tab] web_view] navigateForward]; -} - -- (void)reload:(id)sender -{ - [[[self tab] web_view] reload]; -} - - (void)clearHistory { // FIXME: Reimplement clearing history using WebContent's history. @@ -227,11 +197,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde [[[self tab] web_view] debugRequest:request argument:argument]; } -- (void)viewSource:(id)sender -{ - [[[self tab] web_view] viewSource]; -} - - (void)focusLocationToolbarItem { [self.window makeFirstResponder:self.location_toolbar_item.view]; @@ -308,15 +273,6 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde return YES; } -- (void)updateNavigationButtonStates -{ - auto* navigate_back_button = (NSButton*)[[self navigate_back_toolbar_item] view]; - [navigate_back_button setEnabled:m_can_navigate_back]; - - auto* navigate_forward_button = (NSButton*)[[self navigate_forward_toolbar_item] view]; - [navigate_forward_button setEnabled:m_can_navigate_forward]; -} - - (void)showTabOverview:(id)sender { self.tab.titlebarAppearsTransparent = NO; @@ -474,10 +430,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde - (NSToolbarItem*)navigate_back_toolbar_item { if (!_navigate_back_toolbar_item) { - auto* button = [self create_button:NSImageNameGoBackTemplate - with_action:@selector(navigateBack:) - with_tooltip:@"Navigate back"]; - [button setEnabled:NO]; + auto* button = Ladybird::create_application_button([[[self tab] web_view] view].navigate_back_action(), NSImageNameGoBackTemplate); _navigate_back_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_BACK_IDENTIFIER]; [_navigate_back_toolbar_item setView:button]; @@ -489,10 +442,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde - (NSToolbarItem*)navigate_forward_toolbar_item { if (!_navigate_forward_toolbar_item) { - auto* button = [self create_button:NSImageNameGoForwardTemplate - with_action:@selector(navigateForward:) - with_tooltip:@"Navigate forward"]; - [button setEnabled:NO]; + auto* button = Ladybird::create_application_button([[[self tab] web_view] view].navigate_forward_action(), NSImageNameGoForwardTemplate); _navigate_forward_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_NAVIGATE_FORWARD_IDENTIFIER]; [_navigate_forward_toolbar_item setView:button]; @@ -504,10 +454,7 @@ static NSString* const TOOLBAR_TAB_OVERVIEW_IDENTIFIER = @"ToolbarTabOverviewIde - (NSToolbarItem*)reload_toolbar_item { if (!_reload_toolbar_item) { - auto* button = [self create_button:NSImageNameRefreshTemplate - with_action:@selector(reload:) - with_tooltip:@"Reload page"]; - [button setEnabled:YES]; + auto* button = Ladybird::create_application_button(WebView::Application::the().reload_action(), NSImageNameRefreshTemplate); _reload_toolbar_item = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_RELOAD_IDENTIFIER]; [_reload_toolbar_item setView:button]; diff --git a/UI/Qt/BrowserWindow.cpp b/UI/Qt/BrowserWindow.cpp index ffb21e0d498..a9e81260648 100644 --- a/UI/Qt/BrowserWindow.cpp +++ b/UI/Qt/BrowserWindow.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -26,13 +27,14 @@ #include #include -#include #include #include #include -#include +#include +#include #include #include +#include #include namespace Ladybird { @@ -143,24 +145,9 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow auto* edit_menu = m_hamburger_menu->addMenu("&Edit"); menuBar()->addMenu(edit_menu); - m_copy_selection_action = new QAction("&Copy", this); - m_copy_selection_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - m_copy_selection_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy)); - edit_menu->addAction(m_copy_selection_action); - QObject::connect(m_copy_selection_action, &QAction::triggered, this, &BrowserWindow::copy_selected_text); - - m_paste_action = new QAction("&Paste", this); - m_paste_action->setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv)); - m_paste_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste)); - edit_menu->addAction(m_paste_action); - QObject::connect(m_paste_action, &QAction::triggered, this, &BrowserWindow::paste); - - m_select_all_action = new QAction("Select &All", this); - m_select_all_action->setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv)); - m_select_all_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll)); - edit_menu->addAction(m_select_all_action); - QObject::connect(m_select_all_action, &QAction::triggered, this, &BrowserWindow::select_all); - + edit_menu->addAction(create_application_action(*this, Application::the().copy_selection_action())); + edit_menu->addAction(create_application_action(*this, Application::the().paste_action())); + edit_menu->addAction(create_application_action(*this, Application::the().select_all_action())); edit_menu->addSeparator(); m_find_in_page_action = new QAction("&Find in Page...", this); @@ -330,15 +317,7 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow auto* inspect_menu = m_hamburger_menu->addMenu("&Inspect"); menuBar()->addMenu(inspect_menu); - m_view_source_action = new QAction("View &Source", this); - m_view_source_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv)); - m_view_source_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U)); - inspect_menu->addAction(m_view_source_action); - QObject::connect(m_view_source_action, &QAction::triggered, this, [this] { - if (m_current_tab) { - m_current_tab->view().get_source(); - } - }); + edit_menu->addAction(create_application_action(*this, Application::the().view_source_action())); m_enable_devtools_action = new QAction("Enable &DevTools", this); m_enable_devtools_action->setIcon(load_icon_from_uri("resource://icons/browser/dom-tree.png"sv)); @@ -641,30 +620,6 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, IsPopupWindow QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab); QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab); - m_go_back_action = new QAction("Go Back", this); - connect(m_go_back_action, &QAction::triggered, this, [this] { - if (m_current_tab) - m_current_tab->back(); - }); - m_go_forward_action = new QAction("Go Forward", this); - connect(m_go_forward_action, &QAction::triggered, this, [this] { - if (m_current_tab) - m_current_tab->forward(); - }); - m_reload_action = new QAction("&Reload", this); - connect(m_reload_action, &QAction::triggered, this, [this] { - if (m_current_tab) - m_current_tab->reload(); - }); - - m_go_back_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back)); - m_go_forward_action->setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward)); - m_reload_action->setShortcuts({ QKeySequence(Qt::CTRL | Qt::Key_R), QKeySequence(Qt::Key_F5) }); - - m_go_back_action->setEnabled(false); - m_go_forward_action->setEnabled(false); - m_reload_action->setEnabled(true); - for (int i = 0; i <= 7; ++i) { new QShortcut(QKeySequence(Qt::CTRL | static_cast(Qt::Key_1 + i)), this, [this, i] { if (m_tabs_container->count() <= 1) @@ -727,10 +682,8 @@ void BrowserWindow::devtools_enabled() void BrowserWindow::set_current_tab(Tab* tab) { m_current_tab = tab; - if (tab) { + if (tab) update_displayed_zoom_level(); - tab->update_navigation_buttons_state(); - } } void BrowserWindow::debug_request(ByteString const& request, ByteString const& argument) @@ -801,7 +754,6 @@ void BrowserWindow::initialize_tab(Tab* tab) QObject::connect(tab, &Tab::title_changed, this, &BrowserWindow::tab_title_changed); QObject::connect(tab, &Tab::favicon_changed, this, &BrowserWindow::tab_favicon_changed); QObject::connect(tab, &Tab::audio_play_state_changed, this, &BrowserWindow::tab_audio_play_state_changed); - QObject::connect(tab, &Tab::navigation_buttons_state_changed, this, &BrowserWindow::tab_navigation_buttons_state_changed); QObject::connect(&tab->view(), &WebContentView::urls_dropped, this, [this](auto& urls) { VERIFY(urls.size()); @@ -976,12 +928,6 @@ void BrowserWindow::tab_audio_play_state_changed(int index, Web::HTML::AudioPlay } } -void BrowserWindow::tab_navigation_buttons_state_changed(int index) -{ - auto* tab = as(m_tabs_container->widget(index)); - tab->update_navigation_buttons_state(); -} - QIcon BrowserWindow::icon_for_page_mute_state(Tab& tab) const { switch (tab.view().page_mute_state()) { @@ -1118,14 +1064,6 @@ void BrowserWindow::update_zoom_menu() m_zoom_menu->setTitle(qstring_from_ak_string(zoom_level_text)); } -void BrowserWindow::select_all() -{ - if (!m_current_tab) - return; - - m_current_tab->view().select_all(); -} - void BrowserWindow::show_find_in_page() { if (!m_current_tab) @@ -1134,15 +1072,6 @@ void BrowserWindow::show_find_in_page() m_current_tab->show_find_in_page(); } -void BrowserWindow::paste() -{ - if (!m_current_tab) - return; - - if (m_current_tab->view().on_request_clipboard_text) - m_current_tab->view().paste(m_current_tab->view().on_request_clipboard_text()); -} - void BrowserWindow::update_displayed_zoom_level() { VERIFY(m_current_tab); @@ -1170,17 +1099,6 @@ void BrowserWindow::set_preferred_color_scheme(Web::CSS::PreferredColorScheme co }); } -void BrowserWindow::copy_selected_text() -{ - if (!m_current_tab) - return; - - auto text = m_current_tab->view().selected_text(); - - auto* clipboard = QGuiApplication::clipboard(); - clipboard->setText(qstring_from_ak_string(text)); -} - bool BrowserWindow::event(QEvent* event) { #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) diff --git a/UI/Qt/BrowserWindow.h b/UI/Qt/BrowserWindow.h index 55b31611a5a..b5744616ab3 100644 --- a/UI/Qt/BrowserWindow.h +++ b/UI/Qt/BrowserWindow.h @@ -7,15 +7,12 @@ #pragma once -#include #include #include #include -#include #include #include -#include #include #include #include @@ -42,65 +39,16 @@ public: int tab_count() { return m_tabs_container->count(); } int tab_index(Tab*); + Tab& create_new_tab(Web::HTML::ActivateTab activate_tab); - - QMenu& hamburger_menu() - { - return *m_hamburger_menu; - } - - QAction& go_back_action() - { - return *m_go_back_action; - } - - QAction& go_forward_action() - { - return *m_go_forward_action; - } - - QAction& reload_action() - { - return *m_reload_action; - } - - QAction& new_tab_action() - { - return *m_new_tab_action; - } - - QAction& new_window_action() - { - return *m_new_window_action; - } - - QAction& copy_selection_action() - { - return *m_copy_selection_action; - } - - QAction& select_all_action() - { - return *m_select_all_action; - } - - QAction& find_action() - { - return *m_find_in_page_action; - } - - QAction& paste_action() - { - return *m_paste_action; - } - - QAction& view_source_action() - { - return *m_view_source_action; - } - Tab* current_tab() const { return m_current_tab; } + QMenu& hamburger_menu() const { return *m_hamburger_menu; } + + QAction& new_tab_action() const { return *m_new_tab_action; } + QAction& new_window_action() const { return *m_new_window_action; } + QAction& find_action() const { return *m_find_in_page_action; } + double refresh_rate() const { return m_refresh_rate; } public slots: @@ -109,7 +57,6 @@ public slots: void tab_title_changed(int index, QString const&); void tab_favicon_changed(int index, QIcon const& icon); void tab_audio_play_state_changed(int index, Web::HTML::AudioPlayState); - void tab_navigation_buttons_state_changed(int index); Tab& new_tab_from_url(URL::URL const&, Web::HTML::ActivateTab); Tab& new_tab_from_content(StringView html, Web::HTML::ActivateTab); Tab& new_child_tab(Web::HTML::ActivateTab, Tab& parent, Optional page_index); @@ -132,10 +79,7 @@ public slots: void reset_zoom(); void update_zoom_menu(); void update_displayed_zoom_level(); - void select_all(); void show_find_in_page(); - void paste(); - void copy_selected_text(); protected: bool eventFilter(QObject* obj, QEvent* event) override; @@ -194,16 +138,9 @@ private: QMenu* m_hamburger_menu { nullptr }; - QAction* m_go_back_action { nullptr }; - QAction* m_go_forward_action { nullptr }; - QAction* m_reload_action { nullptr }; QAction* m_new_tab_action { nullptr }; QAction* m_new_window_action { nullptr }; - QAction* m_copy_selection_action { nullptr }; - QAction* m_paste_action { nullptr }; - QAction* m_select_all_action { nullptr }; QAction* m_find_in_page_action { nullptr }; - QAction* m_view_source_action { nullptr }; QAction* m_enable_devtools_action { nullptr }; QAction* m_show_line_box_borders_action { nullptr }; QAction* m_enable_scripting_action { nullptr }; diff --git a/UI/Qt/Menu.cpp b/UI/Qt/Menu.cpp index 669aa8d05d5..52657e47c47 100644 --- a/UI/Qt/Menu.cpp +++ b/UI/Qt/Menu.cpp @@ -73,8 +73,86 @@ private: QPointer m_action; }; -static void initialize_native_control(WebView::Action& action, QAction& qaction) +static void initialize_native_control(WebView::Action& action, QAction& qaction, QPalette const& palette) { + switch (action.id()) { + case WebView::ActionID::NavigateBack: + qaction.setIcon(create_tvg_icon_with_theme_colors("back", palette)); + qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Back)); + break; + case WebView::ActionID::NavigateForward: + qaction.setIcon(create_tvg_icon_with_theme_colors("forward", palette)); + qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Forward)); + break; + case WebView::ActionID::Reload: + qaction.setIcon(create_tvg_icon_with_theme_colors("reload", palette)); + qaction.setShortcuts({ QKeySequence(Qt::CTRL | Qt::Key_R), QKeySequence(Qt::Key_F5) }); + break; + + case WebView::ActionID::CopySelection: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); + qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Copy)); + break; + case WebView::ActionID::Paste: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/paste.png"sv)); + qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::Paste)); + break; + case WebView::ActionID::SelectAll: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/select-all.png"sv)); + qaction.setShortcuts(QKeySequence::keyBindings(QKeySequence::StandardKey::SelectAll)); + break; + + case WebView::ActionID::SearchSelectedText: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv)); + break; + + case WebView::ActionID::ViewSource: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-html.png"sv)); + qaction.setShortcut(QKeySequence(Qt::CTRL | Qt::Key_U)); + break; + + case WebView::ActionID::TakeVisibleScreenshot: + case WebView::ActionID::TakeFullScreenshot: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv)); + break; + + case WebView::ActionID::OpenInNewTab: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv)); + break; + case WebView::ActionID::CopyURL: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); + break; + + case WebView::ActionID::OpenImage: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv)); + break; + case WebView::ActionID::CopyImage: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); + break; + + case WebView::ActionID::OpenAudio: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-sound.png"sv)); + break; + case WebView::ActionID::OpenVideo: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/filetype-video.png"sv)); + break; + case WebView::ActionID::PlayMedia: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/play.png"sv)); + break; + case WebView::ActionID::PauseMedia: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/pause.png"sv)); + break; + case WebView::ActionID::MuteMedia: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/audio-volume-muted.png"sv)); + break; + case WebView::ActionID::UnmuteMedia: + qaction.setIcon(load_icon_from_uri("resource://icons/16x16/audio-volume-high.png"sv)); + break; + + default: + break; + } + if (action.is_checkable()) qaction.setCheckable(true); @@ -123,7 +201,7 @@ QMenu* create_context_menu(QWidget& parent, WebContentView& view, WebView::Menu& QAction* create_application_action(QWidget& parent, WebView::Action& action) { auto* qaction = new QAction(&parent); - initialize_native_control(action, *qaction); + initialize_native_control(action, *qaction, parent.palette()); return qaction; } diff --git a/UI/Qt/Tab.cpp b/UI/Qt/Tab.cpp index b3c925329b4..f7bcd4fc0fb 100644 --- a/UI/Qt/Tab.cpp +++ b/UI/Qt/Tab.cpp @@ -6,24 +6,18 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include -#include +#include #include #include -#include #include -#include #include #include +#include #include #include #include #include -#include -#include -#include #include #include #include @@ -35,9 +29,6 @@ #include #include #include -#include -#include -#include #include namespace Ladybird { @@ -91,13 +82,22 @@ Tab::Tab(BrowserWindow* window, RefPtr parent_client, m_hamburger_button->setMenu(&m_window->hamburger_menu()); m_hamburger_button->setStyleSheet(":menu-indicator {image: none}"); + m_navigate_back_action = create_application_action(*this, view().navigate_back_action()); + m_navigate_forward_action = create_application_action(*this, view().navigate_forward_action()); + m_reload_action = create_application_action(*this, WebView::Application::the().reload_action()); + recreate_toolbar_icons(); m_favicon = default_favicon(); - m_toolbar->addAction(&m_window->go_back_action()); - m_toolbar->addAction(&m_window->go_forward_action()); - m_toolbar->addAction(&m_window->reload_action()); + m_page_context_menu = create_context_menu(*this, view(), view().page_context_menu()); + m_link_context_menu = create_context_menu(*this, view(), view().link_context_menu()); + m_image_context_menu = create_context_menu(*this, view(), view().image_context_menu()); + m_media_context_menu = create_context_menu(*this, view(), view().media_context_menu()); + + m_toolbar->addAction(m_navigate_back_action); + m_toolbar->addAction(m_navigate_forward_action); + m_toolbar->addAction(m_reload_action); m_toolbar->addWidget(m_location_edit); m_toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_hamburger_button_action = m_toolbar->addWidget(m_hamburger_button); @@ -414,17 +414,6 @@ Tab::Tab(BrowserWindow* window, RefPtr parent_client, emit audio_play_state_changed(tab_index(), play_state); }; - view().on_navigation_buttons_state_changed = [this](auto back_enabled, auto forward_enabled) { - m_can_navigate_back = back_enabled; - m_can_navigate_forward = forward_enabled; - emit navigation_buttons_state_changed(tab_index()); - }; - - auto* reload_tab_action = new QAction("&Reload Tab", this); - QObject::connect(reload_tab_action, &QAction::triggered, this, [this]() { - reload(); - }); - auto* duplicate_tab_action = new QAction("&Duplicate Tab", this); QObject::connect(duplicate_tab_action, &QAction::triggered, this, [this]() { m_window->new_tab_from_url(view().url(), Web::HTML::ActivateTab::Yes); @@ -470,7 +459,7 @@ Tab::Tab(BrowserWindow* window, RefPtr parent_client, }); m_context_menu = new QMenu("Context menu", this); - m_context_menu->addAction(reload_tab_action); + m_context_menu->addAction(create_application_action(*this, WebView::Application::the().reload_action())); m_context_menu->addAction(duplicate_tab_action); m_context_menu->addSeparator(); auto* move_tab_menu = m_context_menu->addMenu("Mo&ve Tab"); @@ -482,288 +471,6 @@ Tab::Tab(BrowserWindow* window, RefPtr parent_client, close_multiple_tabs_menu->addAction(close_tabs_to_left_action); close_multiple_tabs_menu->addAction(close_tabs_to_right_action); close_multiple_tabs_menu->addAction(close_other_tabs_action); - - auto* search_selected_text_action = new QAction("&Search for ", this); - search_selected_text_action->setIcon(load_icon_from_uri("resource://icons/16x16/find.png"sv)); - QObject::connect(search_selected_text_action, &QAction::triggered, this, [this]() { - auto const& search_engine = WebView::Application::settings().search_engine(); - if (!search_engine.has_value()) - return; - - auto url_string = search_engine->format_search_query_for_navigation(*m_page_context_menu_search_text); - auto url = URL::Parser::basic_parse(url_string); - VERIFY(url.has_value()); - - m_window->new_tab_from_url(url.release_value(), Web::HTML::ActivateTab::Yes); - }); - - auto take_screenshot = [this](auto type) { - auto& view = this->view(); - - view.take_screenshot(type) - ->when_resolved([](auto const& path) { - WebView::Application::the().display_download_confirmation_dialog("Screenshot"sv, path); - }) - .when_rejected([](auto const& error) { - if (error.is_errno() && error.code() == ECANCELED) - return; - - auto error_message = MUST(String::formatted("{}", error)); - WebView::Application::the().display_error_dialog(error_message); - }); - }; - - auto* take_visible_screenshot_action = new QAction("Take &Visible Screenshot", this); - take_visible_screenshot_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv)); - QObject::connect(take_visible_screenshot_action, &QAction::triggered, this, [take_screenshot]() { - take_screenshot(WebView::ViewImplementation::ScreenshotType::Visible); - }); - - auto* take_full_screenshot_action = new QAction("Take &Full Screenshot", this); - take_full_screenshot_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv)); - QObject::connect(take_full_screenshot_action, &QAction::triggered, this, [take_screenshot]() { - take_screenshot(WebView::ViewImplementation::ScreenshotType::Full); - }); - - m_page_context_menu = new QMenu("Context menu", this); - m_page_context_menu->addAction(&m_window->go_back_action()); - m_page_context_menu->addAction(&m_window->go_forward_action()); - m_page_context_menu->addAction(&m_window->reload_action()); - m_page_context_menu->addSeparator(); - m_page_context_menu->addAction(&m_window->copy_selection_action()); - m_page_context_menu->addAction(&m_window->paste_action()); - m_page_context_menu->addAction(&m_window->select_all_action()); - m_page_context_menu->addSeparator(); - m_page_context_menu->addAction(search_selected_text_action); - m_page_context_menu->addSeparator(); - m_page_context_menu->addAction(take_visible_screenshot_action); - m_page_context_menu->addAction(take_full_screenshot_action); - m_page_context_menu->addSeparator(); - m_page_context_menu->addAction(&m_window->view_source_action()); - - view().on_context_menu_request = [this, search_selected_text_action](Gfx::IntPoint content_position) { - auto const& search_engine = WebView::Application::settings().search_engine(); - - auto selected_text = search_engine.has_value() - ? view().selected_text_with_whitespace_collapsed() - : OptionalNone {}; - TemporaryChange change_url { m_page_context_menu_search_text, AK::move(selected_text) }; - - if (m_page_context_menu_search_text.has_value()) { - auto action_text = search_engine->format_search_query_for_display(*m_page_context_menu_search_text); - search_selected_text_action->setText(qstring_from_ak_string(action_text)); - search_selected_text_action->setVisible(true); - } else { - search_selected_text_action->setVisible(false); - } - - m_page_context_menu->exec(view().map_point_to_global_position(content_position)); - }; - - auto* open_link_in_new_tab_action = new QAction("Open Link in New &Tab", this); - open_link_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv)); - QObject::connect(open_link_in_new_tab_action, &QAction::triggered, this, [this]() { - open_link_in_new_tab(m_link_context_menu_url); - }); - - m_link_context_menu_copy_url_action = new QAction("Copy &Link Address", this); - m_link_context_menu_copy_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - QObject::connect(m_link_context_menu_copy_url_action, &QAction::triggered, this, [this]() { - copy_link_url(m_link_context_menu_url); - }); - - m_link_context_menu = new QMenu("Link context menu", this); - m_link_context_menu->addAction(open_link_in_new_tab_action); - m_link_context_menu->addAction(m_link_context_menu_copy_url_action); - - view().on_link_context_menu_request = [this](auto const& url, Gfx::IntPoint content_position) { - m_link_context_menu_url = url; - - switch (WebView::url_type(url)) { - case WebView::URLType::Email: - m_link_context_menu_copy_url_action->setText("Copy &Email Address"); - break; - case WebView::URLType::Telephone: - m_link_context_menu_copy_url_action->setText("Copy &Phone Number"); - break; - case WebView::URLType::Other: - m_link_context_menu_copy_url_action->setText("Copy &Link Address"); - break; - } - - m_link_context_menu->exec(view().map_point_to_global_position(content_position)); - }; - - auto* open_image_action = new QAction("&Open Image", this); - open_image_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-image.png"sv)); - QObject::connect(open_image_action, &QAction::triggered, this, [this]() { - open_link(m_image_context_menu_url); - }); - - auto* open_image_in_new_tab_action = new QAction("&Open Image in New &Tab", this); - open_image_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv)); - QObject::connect(open_image_in_new_tab_action, &QAction::triggered, this, [this]() { - open_link_in_new_tab(m_image_context_menu_url); - }); - - m_image_context_menu_copy_image_action = new QAction("&Copy Image", this); - m_image_context_menu_copy_image_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - QObject::connect(m_image_context_menu_copy_image_action, &QAction::triggered, this, [this]() { - if (!m_image_context_menu_bitmap.has_value()) - return; - - auto* bitmap = m_image_context_menu_bitmap.value().bitmap(); - if (bitmap == nullptr) - return; - - auto data = Gfx::BMPWriter::encode(*bitmap); - if (data.is_error()) - return; - - auto image = QImage::fromData(data.value().data(), data.value().size(), "BMP"); - if (image.isNull()) - return; - - auto* clipboard = QGuiApplication::clipboard(); - clipboard->setImage(image); - }); - - auto* copy_image_url_action = new QAction("Copy Image &URL", this); - copy_image_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - QObject::connect(copy_image_url_action, &QAction::triggered, this, [this]() { - copy_link_url(m_image_context_menu_url); - }); - - m_image_context_menu = new QMenu("Image context menu", this); - m_image_context_menu->addAction(open_image_action); - m_image_context_menu->addAction(open_image_in_new_tab_action); - m_image_context_menu->addSeparator(); - m_image_context_menu->addAction(m_image_context_menu_copy_image_action); - m_image_context_menu->addAction(copy_image_url_action); - - view().on_image_context_menu_request = [this](auto& image_url, Gfx::IntPoint content_position, Optional const& shareable_bitmap) { - m_image_context_menu_url = image_url; - m_image_context_menu_bitmap = shareable_bitmap; - m_image_context_menu_copy_image_action->setEnabled(shareable_bitmap.has_value()); - - m_image_context_menu->exec(view().map_point_to_global_position(content_position)); - }; - - m_media_context_menu_play_icon = load_icon_from_uri("resource://icons/16x16/play.png"sv); - m_media_context_menu_pause_icon = load_icon_from_uri("resource://icons/16x16/pause.png"sv); - m_media_context_menu_mute_icon = load_icon_from_uri("resource://icons/16x16/audio-volume-muted.png"sv); - m_media_context_menu_unmute_icon = load_icon_from_uri("resource://icons/16x16/audio-volume-high.png"sv); - - m_media_context_menu_play_pause_action = new QAction("&Play", this); - m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_play_icon); - QObject::connect(m_media_context_menu_play_pause_action, &QAction::triggered, this, [this]() { - view().toggle_media_play_state(); - }); - - m_media_context_menu_mute_unmute_action = new QAction("&Mute", this); - m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_mute_icon); - QObject::connect(m_media_context_menu_mute_unmute_action, &QAction::triggered, this, [this]() { - view().toggle_media_mute_state(); - }); - - m_media_context_menu_controls_action = new QAction("Show &Controls", this); - m_media_context_menu_controls_action->setCheckable(true); - QObject::connect(m_media_context_menu_controls_action, &QAction::triggered, this, [this]() { - view().toggle_media_controls_state(); - }); - - m_media_context_menu_loop_action = new QAction("&Loop", this); - m_media_context_menu_loop_action->setCheckable(true); - QObject::connect(m_media_context_menu_loop_action, &QAction::triggered, this, [this]() { - view().toggle_media_loop_state(); - }); - - auto* open_audio_action = new QAction("&Open Audio", this); - open_audio_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-sound.png"sv)); - QObject::connect(open_audio_action, &QAction::triggered, this, [this]() { - open_link(m_media_context_menu_url); - }); - - auto* open_audio_in_new_tab_action = new QAction("Open Audio in New &Tab", this); - open_audio_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv)); - QObject::connect(open_audio_in_new_tab_action, &QAction::triggered, this, [this]() { - open_link_in_new_tab(m_media_context_menu_url); - }); - - auto* copy_audio_url_action = new QAction("Copy Audio &URL", this); - copy_audio_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - QObject::connect(copy_audio_url_action, &QAction::triggered, this, [this]() { - copy_link_url(m_media_context_menu_url); - }); - - m_audio_context_menu = new QMenu("Audio context menu", this); - m_audio_context_menu->addAction(m_media_context_menu_play_pause_action); - m_audio_context_menu->addAction(m_media_context_menu_mute_unmute_action); - m_audio_context_menu->addAction(m_media_context_menu_controls_action); - m_audio_context_menu->addAction(m_media_context_menu_loop_action); - m_audio_context_menu->addSeparator(); - m_audio_context_menu->addAction(open_audio_action); - m_audio_context_menu->addAction(open_audio_in_new_tab_action); - m_audio_context_menu->addSeparator(); - m_audio_context_menu->addAction(copy_audio_url_action); - - auto* open_video_action = new QAction("&Open Video", this); - open_video_action->setIcon(load_icon_from_uri("resource://icons/16x16/filetype-video.png"sv)); - QObject::connect(open_video_action, &QAction::triggered, this, [this]() { - open_link(m_media_context_menu_url); - }); - - auto* open_video_in_new_tab_action = new QAction("Open Video in New &Tab", this); - open_video_in_new_tab_action->setIcon(load_icon_from_uri("resource://icons/16x16/new-tab.png"sv)); - QObject::connect(open_video_in_new_tab_action, &QAction::triggered, this, [this]() { - open_link_in_new_tab(m_media_context_menu_url); - }); - - auto* copy_video_url_action = new QAction("Copy Video &URL", this); - copy_video_url_action->setIcon(load_icon_from_uri("resource://icons/16x16/edit-copy.png"sv)); - QObject::connect(copy_video_url_action, &QAction::triggered, this, [this]() { - copy_link_url(m_media_context_menu_url); - }); - - m_video_context_menu = new QMenu("Video context menu", this); - m_video_context_menu->addAction(m_media_context_menu_play_pause_action); - m_video_context_menu->addAction(m_media_context_menu_mute_unmute_action); - m_video_context_menu->addAction(m_media_context_menu_controls_action); - m_video_context_menu->addAction(m_media_context_menu_loop_action); - m_video_context_menu->addSeparator(); - m_video_context_menu->addAction(open_video_action); - m_video_context_menu->addAction(open_video_in_new_tab_action); - m_video_context_menu->addSeparator(); - m_video_context_menu->addAction(copy_video_url_action); - - view().on_media_context_menu_request = [this](Gfx::IntPoint content_position, Web::Page::MediaContextMenu const& menu) { - m_media_context_menu_url = menu.media_url; - - if (menu.is_playing) { - m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_pause_icon); - m_media_context_menu_play_pause_action->setText("&Pause"); - } else { - m_media_context_menu_play_pause_action->setIcon(m_media_context_menu_play_icon); - m_media_context_menu_play_pause_action->setText("&Play"); - } - - if (menu.is_muted) { - m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_unmute_icon); - m_media_context_menu_mute_unmute_action->setText("Un&mute"); - } else { - m_media_context_menu_mute_unmute_action->setIcon(m_media_context_menu_mute_icon); - m_media_context_menu_mute_unmute_action->setText("&Mute"); - } - - m_media_context_menu_controls_action->setChecked(menu.has_user_agent_controls); - m_media_context_menu_loop_action->setChecked(menu.is_looping); - - auto screen_position = view().map_point_to_global_position(content_position); - if (menu.is_video) - m_video_context_menu->exec(screen_position); - else - m_audio_context_menu->exec(screen_position); - }; } Tab::~Tab() = default; @@ -796,37 +503,6 @@ void Tab::load_html(StringView html) view().load_html(html); } -void Tab::back() -{ - view().traverse_the_history_by_delta(-1); -} - -void Tab::forward() -{ - view().traverse_the_history_by_delta(1); -} - -void Tab::reload() -{ - view().reload(); -} - -void Tab::open_link(URL::URL const& url) -{ - view().on_link_click(url, "", 0); -} - -void Tab::open_link_in_new_tab(URL::URL const& url) -{ - view().on_link_click(url, "_blank", Web::UIEvents::Mod_Ctrl); -} - -void Tab::copy_link_url(URL::URL const& url) -{ - auto* clipboard = QGuiApplication::clipboard(); - clipboard->setText(qstring_from_ak_string(WebView::url_text_to_copy(url))); -} - void Tab::location_edit_return_pressed() { if (m_location_edit->text().isEmpty()) @@ -876,15 +552,6 @@ void Tab::update_hover_label() m_hover_label->raise(); } -void Tab::update_navigation_buttons_state() -{ - if (m_window->current_tab() != this) - return; - - m_window->go_back_action().setEnabled(m_can_navigate_back); - m_window->go_forward_action().setEnabled(m_can_navigate_forward); -} - bool Tab::event(QEvent* event) { if (event->type() == QEvent::PaletteChange) { @@ -897,9 +564,9 @@ bool Tab::event(QEvent* event) void Tab::recreate_toolbar_icons() { - m_window->go_back_action().setIcon(create_tvg_icon_with_theme_colors("back", palette())); - m_window->go_forward_action().setIcon(create_tvg_icon_with_theme_colors("forward", palette())); - m_window->reload_action().setIcon(create_tvg_icon_with_theme_colors("reload", palette())); + m_navigate_back_action->setIcon(create_tvg_icon_with_theme_colors("back", palette())); + m_navigate_forward_action->setIcon(create_tvg_icon_with_theme_colors("forward", palette())); + m_reload_action->setIcon(create_tvg_icon_with_theme_colors("reload", palette())); m_window->new_tab_action().setIcon(create_tvg_icon_with_theme_colors("new_tab", palette())); m_hamburger_button->setIcon(create_tvg_icon_with_theme_colors("hamburger", palette())); } diff --git a/UI/Qt/Tab.h b/UI/Qt/Tab.h index 6bb624ad9dc..ffc9069fe57 100644 --- a/UI/Qt/Tab.h +++ b/UI/Qt/Tab.h @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -56,10 +55,6 @@ public: void navigate(URL::URL const&); void load_html(StringView); - void back(); - void forward(); - void reload(); - void debug_request(ByteString const& request, ByteString const& argument = ""); void open_file(); @@ -74,8 +69,6 @@ public: QMenu* context_menu() const { return m_context_menu; } - void update_navigation_buttons_state(); - QToolButton* hamburger_button() const { return m_hamburger_button; } void update_hover_label(); @@ -98,17 +91,13 @@ signals: void title_changed(int id, QString const&); void favicon_changed(int id, QIcon const&); void audio_play_state_changed(int id, Web::HTML::AudioPlayState); - void navigation_buttons_state_changed(int id); private: virtual void resizeEvent(QResizeEvent*) override; virtual bool event(QEvent*) override; void recreate_toolbar_icons(); - - void open_link(URL::URL const&); - void open_link_in_new_tab(URL::URL const&); - void copy_link_url(URL::URL const&); + int tab_index(); QBoxLayout* m_layout { nullptr }; QToolBar* m_toolbar { nullptr }; @@ -125,39 +114,17 @@ private: QIcon m_favicon; QMenu* m_context_menu { nullptr }; - QMenu* m_page_context_menu { nullptr }; - Optional m_page_context_menu_search_text; - QMenu* m_link_context_menu { nullptr }; - QAction* m_link_context_menu_copy_url_action { nullptr }; - URL::URL m_link_context_menu_url; - QMenu* m_image_context_menu { nullptr }; - QAction* m_image_context_menu_copy_image_action { nullptr }; - Optional m_image_context_menu_bitmap; - URL::URL m_image_context_menu_url; - - QMenu* m_audio_context_menu { nullptr }; - QMenu* m_video_context_menu { nullptr }; - QIcon m_media_context_menu_play_icon; - QIcon m_media_context_menu_pause_icon; - QIcon m_media_context_menu_mute_icon; - QIcon m_media_context_menu_unmute_icon; - QAction* m_media_context_menu_play_pause_action { nullptr }; - QAction* m_media_context_menu_mute_unmute_action { nullptr }; - QAction* m_media_context_menu_controls_action { nullptr }; - QAction* m_media_context_menu_loop_action { nullptr }; - URL::URL m_media_context_menu_url; - + QMenu* m_media_context_menu { nullptr }; QMenu* m_select_dropdown { nullptr }; - int tab_index(); + QAction* m_navigate_back_action { nullptr }; + QAction* m_navigate_forward_action { nullptr }; + QAction* m_reload_action { nullptr }; QPointer m_dialog; - - bool m_can_navigate_back { false }; - bool m_can_navigate_forward { false }; }; }