aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--include/ImageViewer.hpp1
-rw-r--r--include/QuickMedia.hpp1
-rw-r--r--plugins/Info.hpp15
-rw-r--r--src/NetUtils.cpp2
-rw-r--r--src/QuickMedia.cpp65
-rw-r--r--src/plugins/Info.cpp47
7 files changed, 117 insertions, 16 deletions
diff --git a/README.md b/README.md
index e469106..6058e2e 100644
--- a/README.md
+++ b/README.md
@@ -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