From d74766245facd48805c2576c711d20cafa33aa35 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 18 Sep 2021 18:07:10 +0200 Subject: Show option to open saucenao result urls ctrl+c for info should only copy the url --- plugins/Info.hpp | 3 ++ plugins/Page.hpp | 2 ++ plugins/Saucenao.hpp | 6 ++-- src/QuickMedia.cpp | 46 ++++++------------------------ src/plugins/Info.cpp | 59 ++++++++++++++++++++++++++++---------- src/plugins/Page.cpp | 24 ++++++++++++++++ src/plugins/Saucenao.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 157 insertions(+), 56 deletions(-) diff --git a/plugins/Info.hpp b/plugins/Info.hpp index af62282..fbf7a41 100644 --- a/plugins/Info.hpp +++ b/plugins/Info.hpp @@ -10,7 +10,10 @@ namespace QuickMedia { PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; bool submit_is_async() const override { return false; } + void copy_to_clipboard(const BodyItem *body_item) const override; + static std::shared_ptr add_url(const std::string &url); static std::shared_ptr add_reverse_image_search(const std::string &image_url); + static std::shared_ptr add_google_search(const std::string &search_term); }; } \ No newline at end of file diff --git a/plugins/Page.hpp b/plugins/Page.hpp index d5daa3d..4fed1c8 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -74,6 +74,8 @@ namespace QuickMedia { virtual void on_navigate_to_page(Body *body) { (void)body; } virtual void cancel_operation() {} + virtual void copy_to_clipboard(const BodyItem *body_item) const; + std::unique_ptr create_body(bool plain_text_list = false, bool prefer_card_view = false); std::unique_ptr create_search_bar(const std::string &placeholder_text, int search_delay); diff --git a/plugins/Saucenao.hpp b/plugins/Saucenao.hpp index 297dde3..d58b424 100644 --- a/plugins/Saucenao.hpp +++ b/plugins/Saucenao.hpp @@ -7,11 +7,9 @@ namespace QuickMedia { public: SaucenaoPage(Program *program, const std::string &path, bool is_local) : LazyFetchPage(program), path(path), is_local(is_local) {} const char* get_title() const override { return "SauceNAO"; } - PluginResult submit(const std::string&, const std::string&, std::vector&) override { - return PluginResult::OK; - } + bool submit_is_async() const override { return false; } + PluginResult submit(const std::string &title, const std::string &url, std::vector &result_tabs) override; PluginResult lazy_fetch(BodyItems &result_items) override; - bool is_single_page() const override { return true; } private: std::string path; bool is_local; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 1095aaa..9b31580 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1719,32 +1719,6 @@ namespace QuickMedia { } } - static void copy_body_item_text_to_clipboard(BodyItem *body_item) { - if(!body_item) - return; - - std::string title = body_item->get_title(); - std::string author = body_item->get_author(); - std::string description = body_item->get_description(); - - std::string clipboard = std::move(title); - - if(!author.empty()) { - if(!clipboard.empty()) - clipboard += '\n'; - clipboard += std::move(author); - } - - if(!description.empty()) { - if(!clipboard.empty()) - clipboard += '\n'; - clipboard += std::move(description); - } - - if(!clipboard.empty()) - sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end())); - } - static void set_search_bar_to_body_item_text(BodyItem *body_item, SearchBar *search_bar) { if(!body_item || !search_bar) return; @@ -1808,6 +1782,9 @@ namespace QuickMedia { window_size.y = window_size_u.y; std::function submit_handler = [this, &submit_handler, &after_submit_handler, &tabs, &tab_associated_data, &ui_tabs, &loop_running, &redraw](const std::string &search_text) { + sf::Event event; + while(window.pollEvent(event)) { common_event_handler(event); } + const int selected_tab = ui_tabs.get_selected(); auto selected_item = tabs[selected_tab].body->get_selected_shared(); if(!selected_item && search_text.empty()) @@ -2145,7 +2122,9 @@ namespace QuickMedia { } } } else if(event.key.code == sf::Keyboard::C && event.key.control) { - copy_body_item_text_to_clipboard(tabs[selected_tab].body->get_selected()); + BodyItem *selected_item = tabs[selected_tab].body->get_selected(); + if(selected_item) + tabs[selected_tab].page->copy_to_clipboard(selected_item); } else if(event.key.code == sf::Keyboard::I && event.key.control) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(show_info_page(selected_item, false)) @@ -4135,17 +4114,8 @@ namespace QuickMedia { selected_file_for_upload.clear(); } else if(event.key.code == sf::Keyboard::C && event.key.control) { BodyItem *selected_item = thread_body->get_selected(); - if(selected_item) { - std::string title = selected_item->get_title(); - std::string description = selected_item->get_description(); - std::string clipboard = title; - if(!clipboard.empty()) { - clipboard += '\n'; - clipboard += std::move(description); - } - if(!clipboard.empty()) - sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end())); - } + if(selected_item) + thread_page->copy_to_clipboard(selected_item); } else if(event.key.code == sf::Keyboard::I && event.key.control) { BodyItem *selected_item = thread_body->get_selected(); if(show_info_page(selected_item, true)) { diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp index b488eba..05efc44 100644 --- a/src/plugins/Info.cpp +++ b/src/plugins/Info.cpp @@ -5,39 +5,63 @@ #include "../../include/Program.hpp" #include "../../include/Notification.hpp" #include "../../include/Storage.hpp" +#include namespace QuickMedia { static const char *REVERSE_IMAGE_SEARCH_URL = "reverse-image-search://"; + static const char *GOOGLE_SEARCH_URL = "google-search://"; static bool is_youtube_url(const std::string &url) { return url.find("youtube.com/") != std::string::npos || url.find("youtu.be/") != std::string::npos; } + static PluginResult open_with_browser(const std::string &url) { + const char *launch_program = "xdg-open"; + if(!is_program_executable_by_name("xdg-open")) { + launch_program = getenv("BROWSER"); + if(!launch_program) { + show_notification("QuickMedia", "xdg-utils which provides xdg-open needs to be installed to open urls. Alternatively set the $BROWSER environment variable to a browser", Urgency::CRITICAL); + return PluginResult::ERR; + } + } + + std::string url_modified = url; + if(strncmp(url.c_str(), "http://", 7) != 0 && strncmp(url.c_str(), "https://", 8) != 0) + url_modified = "https://" + url; + + const char *args[] = { launch_program, url_modified.c_str(), nullptr }; + return exec_program_async(args, nullptr) == 0 ? PluginResult::OK : PluginResult::ERR; + } + PluginResult InfoPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { if(string_starts_with(url, REVERSE_IMAGE_SEARCH_URL)) { std::string image_url = url.substr(strlen(REVERSE_IMAGE_SEARCH_URL)); result_tabs.push_back(Tab{create_body(), std::make_unique(program, image_url, false), nullptr}); return PluginResult::OK; + } else if(string_starts_with(url, GOOGLE_SEARCH_URL)) { + const std::string search_term = url.substr(strlen(GOOGLE_SEARCH_URL)); + const std::string search_url = "https://www.google.com/search?q=" + url_param_encode(search_term); + return open_with_browser(search_url); } else if(is_youtube_url(url)) { result_tabs.push_back(Tab{nullptr, std::make_unique(program, url), nullptr}); return PluginResult::OK; } else { - const char *launch_program = "xdg-open"; - if(!is_program_executable_by_name("xdg-open")) { - launch_program = getenv("BROWSER"); - if(!launch_program) { - show_notification("QuickMedia", "xdg-utils which provides xdg-open needs to be installed to open urls. Alternatively set the $BROWSER environment variable to a browser", Urgency::CRITICAL); - return PluginResult::ERR; - } - } - - std::string url_modified = url; - if(strncmp(url.c_str(), "http://", 7) != 0 && strncmp(url.c_str(), "https://", 8) != 0) - url_modified = "https://" + url; + return open_with_browser(url); + } + } - const char *args[] = { launch_program, url_modified.c_str(), nullptr }; - return exec_program_async(args, nullptr) == 0 ? PluginResult::OK : PluginResult::ERR; + void InfoPage::copy_to_clipboard(const BodyItem *body_item) const { + std::string url; + if(string_starts_with(body_item->url, REVERSE_IMAGE_SEARCH_URL)) { + url = body_item->url.substr(strlen(REVERSE_IMAGE_SEARCH_URL)); + } else if(string_starts_with(body_item->url, GOOGLE_SEARCH_URL)) { + url = body_item->url.substr(strlen(GOOGLE_SEARCH_URL)); + } else { + url = body_item->url; } + + if(!url.empty()) + sf::Clipboard::setString(sf::String::fromUtf8(url.begin(), url.end())); } // static @@ -58,4 +82,11 @@ namespace QuickMedia { body_item->url = REVERSE_IMAGE_SEARCH_URL + image_url; return body_item; } + + // static + std::shared_ptr InfoPage::add_google_search(const std::string &search_term) { + auto body_item = BodyItem::create("Search for \"" + search_term + "\" with google search"); + body_item->url = GOOGLE_SEARCH_URL + search_term; + return body_item; + } } \ No newline at end of file diff --git a/src/plugins/Page.cpp b/src/plugins/Page.cpp index 935d214..6cf3790 100644 --- a/src/plugins/Page.cpp +++ b/src/plugins/Page.cpp @@ -3,6 +3,7 @@ #include "../../include/Theme.hpp" #include "../../include/Storage.hpp" #include "../../include/QuickMedia.hpp" +#include #include namespace QuickMedia { @@ -44,6 +45,29 @@ namespace QuickMedia { return program->load_manga_content_storage(service_name, manga_title, manga_url, manga_id); } + void Page::copy_to_clipboard(const BodyItem *body_item) const { + std::string title = body_item->get_title(); + std::string author = body_item->get_author(); + std::string description = body_item->get_description(); + + std::string clipboard = std::move(title); + + if(!author.empty()) { + if(!clipboard.empty()) + clipboard += '\n'; + clipboard += std::move(author); + } + + if(!description.empty()) { + if(!clipboard.empty()) + clipboard += '\n'; + clipboard += std::move(description); + } + + if(!clipboard.empty()) + sf::Clipboard::setString(sf::String::fromUtf8(clipboard.begin(), clipboard.end())); + } + PluginResult BookmarksPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { return redirect_page->submit(title, url, result_tabs); } diff --git a/src/plugins/Saucenao.cpp b/src/plugins/Saucenao.cpp index d81e233..ccb01f9 100644 --- a/src/plugins/Saucenao.cpp +++ b/src/plugins/Saucenao.cpp @@ -1,8 +1,78 @@ #include "../../plugins/Saucenao.hpp" +#include "../../plugins/Info.hpp" #include "../../include/StringUtils.hpp" #include namespace QuickMedia { + class SaucenaoBodyItemUrls : public BodyItemExtra { + public: + std::vector urls; + }; + + static int string_views_equal_case_insensitive(const QuickMediaStringView str1, const QuickMediaStringView str2) { + if(str2.size != str1.size) + return 1; + + for(size_t i = 0; i < str1.size; ++i) { + char c1 = str1.data[i]; + char c2 = str2.data[i]; + if(to_upper(c1) != to_upper(c2)) + return 1; + } + + return 0; + } + + static QuickMediaHtmlAttribute* get_attribute_by_name(QuickMediaHtmlNode *node, QuickMediaStringView name) { + for(QuickMediaHtmlAttribute *attr = node->first_attribute; attr; attr = attr->next) { + if(string_views_equal_case_insensitive(attr->key, name) == 0) + return attr; + } + return NULL; + } + + static void match_node_get_urls(QuickMediaHtmlNode *node, std::vector &urls) { + if(node->is_tag && string_views_equal_case_insensitive(node->name, {"a", 1}) == 0) { + QuickMediaHtmlAttribute *attr = get_attribute_by_name(node, {"href", 4}); + if(attr) + urls.emplace_back(attr->value.data, attr->value.size); + } + + QuickMediaHtmlChildNode *child_node = node->first_child; + while(child_node) { + match_node_get_urls(&child_node->node, urls); + child_node = child_node->next; + } + } + + static std::string get_first_line(const std::string &str) { + const size_t line_end = str.find('\n'); + if(line_end == std::string::npos) + return str; + else + return str.substr(0, line_end); + } + + PluginResult SaucenaoPage::submit(const std::string &title, const std::string&, std::vector &result_tabs) { + if(!submit_body_item) + return PluginResult::OK; + + SaucenaoBodyItemUrls *urls = static_cast(submit_body_item->extra.get()); + BodyItems body_items; + + for(const std::string &url : urls->urls) { + // Ignore saucenao info pages + if(url.find("saucenao.com") == std::string::npos) + body_items.push_back(InfoPage::add_url(url)); + } + body_items.push_back(InfoPage::add_google_search(get_first_line(title))); + + auto body = create_body(); + body->set_items(std::move(body_items)); + result_tabs.push_back(Tab{ std::move(body), std::make_unique(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) }); + return PluginResult::OK; + } + PluginResult SaucenaoPage::lazy_fetch(BodyItems &result_items) { std::vector additional_args; if(is_local) { @@ -31,6 +101,9 @@ namespace QuickMedia { title = title.erase(0, p_index + 1); title = strip(title); auto item = BodyItem::create(title); + auto urls = std::make_shared(); + match_node_get_urls(node->node, urls->urls); + item->extra = std::move(urls); item_data->push_back(std::move(item)); } return 0; -- cgit v1.2.3