diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | include/ImageViewer.hpp | 1 | ||||
-rw-r--r-- | include/QuickMedia.hpp | 1 | ||||
-rw-r--r-- | plugins/Info.hpp | 15 | ||||
-rw-r--r-- | src/NetUtils.cpp | 2 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 65 | ||||
-rw-r--r-- | src/plugins/Info.cpp | 47 |
7 files changed, 117 insertions, 16 deletions
@@ -58,7 +58,7 @@ Press `Ctrl + C` to copy the text of the selected item to the clipboard.\ Press `U` in matrix or in a 4chan thread to bring up the file manager to choose a file to upload.\ Press `Ctrl + V` to upload media to room in matrix if the clipboard contains a valid absolute filepath.\ Press `Ctrl + D` to remove the file that was previously selected with `U` in a 4chan thread.\ -Press `Ctrl + I` to reverse image search the selected image on 4chan or matrix.\ +Press `Ctrl + I` to reverse image search the selected image on 4chan or matrix, or to select the url in the text to open in a browser.\ Press `Ctrl+Alt+Arrow up` / `Ctrl+Alt+Arrow down` or `Ctrl+Alt+K` / `Ctrl+Alt+J` to view the room above/below the selected room in matrix.\ Press `Ctrl + S` to save the displaying image/video/audio (does currently not work for manga pages).\ Press `Ctrl + Enter` to submit text, ignoring the selected item (when saving a file or selecting a server for matrix room directory).\ diff --git a/include/ImageViewer.hpp b/include/ImageViewer.hpp index 74d93c1..43384b4 100644 --- a/include/ImageViewer.hpp +++ b/include/ImageViewer.hpp @@ -9,6 +9,7 @@ #include <SFML/System/Clock.hpp> #include <SFML/Window/Cursor.hpp> #include <thread> +#include <memory> namespace sf { class RenderWindow; diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 0ef5273..6ab910d 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -108,6 +108,7 @@ namespace QuickMedia { void event_idle_handler(const sf::Event &event); void idle_active_handler(); void update_idle_state(); + bool show_info_page(BodyItem *body_item, bool include_reverse_image_search); void page_loop_render(sf::RenderWindow &window, std::vector<Tab> &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs); using PageLoopSubmitHandler = std::function<void(const std::vector<Tab> &new_tabs)>; void page_loop(std::vector<Tab> &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr); diff --git a/plugins/Info.hpp b/plugins/Info.hpp new file mode 100644 index 0000000..d2d9db3 --- /dev/null +++ b/plugins/Info.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "Page.hpp" + +namespace QuickMedia { + class InfoPage : public Page { + public: + InfoPage(Program *program) : Page(program) {} + const char* get_title() const override { return "Info"; } + PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) 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); + }; +}
\ No newline at end of file diff --git a/src/NetUtils.cpp b/src/NetUtils.cpp index de908b1..f120cc0 100644 --- a/src/NetUtils.cpp +++ b/src/NetUtils.cpp @@ -1636,6 +1636,8 @@ namespace QuickMedia { // Also checks for balanced parentheses to allow text such as: (see: example.com/) that excludes the last parenthesis. std::vector<Range> extract_urls(const std::string &str) { std::vector<Range> ranges; + if(str.empty()) + return ranges; int parentheses_depth = 0; bool is_valid_url = false; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index ea77bff..dfec05f 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -13,6 +13,7 @@ #include "../plugins/FileManager.hpp" #include "../plugins/Pipe.hpp" #include "../plugins/Saucenao.hpp" +#include "../plugins/Info.hpp" #include "../include/Scale.hpp" #include "../include/Program.hpp" #include "../include/VideoPlayer.hpp" @@ -1411,6 +1412,46 @@ namespace QuickMedia { pipe_selected_text = text; } + static bool is_url_video(const std::string &url) { + return string_ends_with(url, ".webm") || string_ends_with(url, ".mp4") || string_ends_with(url, ".mkv") || string_ends_with(url, ".gif"); + } + + bool Program::show_info_page(BodyItem *body_item, bool include_reverse_image_search) { + if(!body_item) + return false; + + std::string title = body_item->get_title(); + std::string description = body_item->get_description(); + std::string text = std::move(title); + if(!description.empty()) { + if(!text.empty()) + text += '\n'; + text += std::move(description); + } + + auto body = create_body(); + + if(include_reverse_image_search && !body_item->url.empty() && !body_item->thumbnail_url.empty()) { + std::string image_url = body_item->url; + if(is_url_video(body_item->url)) + image_url = body_item->thumbnail_url; + body->items.push_back(InfoPage::add_reverse_image_search(image_url)); + } + + std::vector<std::string> urls = ranges_get_strings(text, extract_urls(text)); + for(const std::string &url : urls) { + body->items.push_back(InfoPage::add_url(url)); + } + + if(body->items.empty()) + return false; + + std::vector<Tab> info_tabs; + info_tabs.push_back(Tab{std::move(body), std::make_unique<InfoPage>(this), nullptr}); + page_loop(info_tabs); + return true; + } + void Program::page_loop_render(sf::RenderWindow &window, std::vector<Tab> &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, window_size, true); @@ -1770,14 +1811,19 @@ namespace QuickMedia { 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'; + std::string clipboard = std::move(title); + 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())); } + } 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)) + redraw = true; } } } @@ -2995,10 +3041,6 @@ namespace QuickMedia { idle = true; } - static bool is_url_video(const std::string &url) { - return string_ends_with(url, ".webm") || string_ends_with(url, ".mp4") || string_ends_with(url, ".gif"); - } - void Program::image_board_thread_page(ImageBoardThreadPage *thread_page, Body *thread_body) { // TODO: Instead of using stage here, use different pages for each stage enum class NavigationStage { @@ -3294,14 +3336,7 @@ namespace QuickMedia { } } else if(event.key.code == sf::Keyboard::I && event.key.control) { BodyItem *selected_item = thread_body->get_selected(); - if(selected_item && !selected_item->url.empty() && !selected_item->thumbnail_url.empty()) { - std::string image_url = selected_item->url; - if(is_url_video(selected_item->url)) - image_url = selected_item->thumbnail_url; - - std::vector<Tab> saucenao_tabs; - saucenao_tabs.push_back(Tab{create_body(), std::make_unique<SaucenaoPage>(this, image_url, false), nullptr}); - page_loop(saucenao_tabs); + if(show_info_page(selected_item, true)) { redraw = true; frame_skip_text_entry = true; } diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp new file mode 100644 index 0000000..b1943b3 --- /dev/null +++ b/src/plugins/Info.cpp @@ -0,0 +1,47 @@ +#include "../../plugins/Info.hpp" +#include "../../plugins/Saucenao.hpp" +#include "../../include/StringUtils.hpp" +#include "../../include/Program.hpp" +#include "../../include/Notification.hpp" + +namespace QuickMedia { + static const char *REVERSE_IMAGE_SEARCH_URL = "reverse-image-search://"; + + 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 { + 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; + } + } + + // static + std::shared_ptr<BodyItem> InfoPage::add_url(const std::string &url) { + auto body_item = BodyItem::create("Open " + url + " in a browser"); + body_item->url = url; + return body_item; + } + + // static + std::shared_ptr<BodyItem> InfoPage::add_reverse_image_search(const std::string &image_url) { + auto body_item = BodyItem::create("Reverse search the image with SauceNAO"); + body_item->url = REVERSE_IMAGE_SEARCH_URL + image_url; + return body_item; + } +}
\ No newline at end of file |