aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/Info.hpp3
-rw-r--r--plugins/Page.hpp2
-rw-r--r--plugins/Saucenao.hpp6
-rw-r--r--src/QuickMedia.cpp46
-rw-r--r--src/plugins/Info.cpp59
-rw-r--r--src/plugins/Page.cpp24
-rw-r--r--src/plugins/Saucenao.cpp73
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<Tab> &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<BodyItem> add_url(const std::string &url);
static std::shared_ptr<BodyItem> add_reverse_image_search(const std::string &image_url);
+ static std::shared_ptr<BodyItem> 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<Body> create_body(bool plain_text_list = false, bool prefer_card_view = false);
std::unique_ptr<SearchBar> 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<Tab>&) override {
- return PluginResult::OK;
- }
+ bool submit_is_async() const override { return false; }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &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<void(const std::string&)> 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 <SFML/Window/Clipboard.hpp>
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<Tab> &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<SaucenaoPage>(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<YoutubeVideoPage>(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<BodyItem> 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 <SFML/Window/Clipboard.hpp>
#include <json/reader.h>
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<Tab> &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 <quickmedia/HtmlSearch.h>
namespace QuickMedia {
+ class SaucenaoBodyItemUrls : public BodyItemExtra {
+ public:
+ std::vector<std::string> 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<std::string> &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<Tab> &result_tabs) {
+ if(!submit_body_item)
+ return PluginResult::OK;
+
+ SaucenaoBodyItemUrls *urls = static_cast<SaucenaoBodyItemUrls*>(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<InfoPage>(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) });
+ return PluginResult::OK;
+ }
+
PluginResult SaucenaoPage::lazy_fetch(BodyItems &result_items) {
std::vector<CommandArg> 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<SaucenaoBodyItemUrls>();
+ match_node_get_urls(node->node, urls->urls);
+ item->extra = std::move(urls);
item_data->push_back(std::move(item));
}
return 0;