aboutsummaryrefslogtreecommitdiff
path: root/src/QuickMedia.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r--src/QuickMedia.cpp2223
1 files changed, 833 insertions, 1390 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 3922ceb..01e244a 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -5,7 +5,6 @@
#include "../plugins/Youtube.hpp"
#include "../plugins/Pornhub.hpp"
#include "../plugins/Fourchan.hpp"
-#include "../plugins/Dmenu.hpp"
#include "../plugins/NyaaSi.hpp"
#include "../plugins/Matrix.hpp"
#include "../plugins/FileManager.hpp"
@@ -166,13 +165,102 @@ static sf::Color interpolate_colors(sf::Color source, sf::Color target, double p
}
namespace QuickMedia {
+ class HistoryPage : public Page {
+ public:
+ HistoryPage(Program *program, Page *search_page) : Page(program), search_page(search_page) {}
+ const char* get_title() const override { return "History"; }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override {
+ return search_page->submit(title, url, result_tabs);
+ }
+ private:
+ Page *search_page;
+ };
+
+ class RecommendedPage : public Page {
+ public:
+ RecommendedPage(Program *program, Page *search_page) : Page(program), search_page(search_page) {}
+ const char* get_title() const override { return "Recommended"; }
+ PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override {
+ return search_page->submit(title, url, result_tabs);
+ }
+ private:
+ Page *search_page;
+ };
+
+ // TODO: Make asynchronous
+ static void fill_recommended_items_from_json(const Json::Value &recommended_json, BodyItems &body_items) {
+ assert(recommended_json.isObject());
+
+ std::vector<std::pair<std::string, Json::Value>> recommended_items(recommended_json.size());
+ /* TODO: Optimize member access */
+ for(auto &member_name : recommended_json.getMemberNames()) {
+ Json::Value recommended_item = recommended_json[member_name];
+ if(recommended_item.isObject())
+ recommended_items.push_back(std::make_pair(member_name, std::move(recommended_item)));
+ }
+
+ /* TODO: Better algorithm for recommendations */
+ std::sort(recommended_items.begin(), recommended_items.end(), [](std::pair<std::string, Json::Value> &a, std::pair<std::string, Json::Value> &b) {
+ Json::Value &a_timestamp_json = a.second["recommended_timestamp"];
+ Json::Value &b_timestamp_json = b.second["recommended_timestamp"];
+ int64_t a_timestamp = 0;
+ int64_t b_timestamp = 0;
+ if(a_timestamp_json.isNumeric())
+ a_timestamp = a_timestamp_json.asInt64();
+ if(b_timestamp_json.isNumeric())
+ b_timestamp = b_timestamp_json.asInt64();
+
+ Json::Value &a_recommended_count_json = a.second["recommended_count"];
+ Json::Value &b_recommended_count_json = b.second["recommended_count"];
+ int64_t a_recommended_count = 0;
+ int64_t b_recommended_count = 0;
+ if(a_recommended_count_json.isNumeric())
+ a_recommended_count = a_recommended_count_json.asInt64();
+ if(b_recommended_count_json.isNumeric())
+ b_recommended_count = b_recommended_count_json.asInt64();
+
+ /* Put frequently recommended videos on top of recommendations. Each recommendation count is worth 5 minutes */
+ a_timestamp += (300 * a_recommended_count);
+ b_timestamp += (300 * b_recommended_count);
+
+ return a_timestamp > b_timestamp;
+ });
+
+ for(auto it = recommended_items.begin(); it != recommended_items.end(); ++it) {
+ const std::string &recommended_item_id = it->first;
+ Json::Value &recommended_item = it->second;
+
+ int64_t watched_count = 0;
+ const Json::Value &watched_count_json = recommended_item["watched_count"];
+ if(watched_count_json.isNumeric())
+ watched_count = watched_count_json.asInt64();
+
+ /* TODO: Improve recommendations with some kind of algorithm. Videos we have seen should be recommended in some cases */
+ if(watched_count != 0)
+ continue;
+
+ const Json::Value &recommended_title_json = recommended_item["title"];
+ if(!recommended_title_json.isString())
+ continue;
+
+ auto body_item = BodyItem::create(recommended_title_json.asString());
+ body_item->url = "https://www.youtube.com/watch?v=" + recommended_item_id;
+ body_item->thumbnail_url = "https://img.youtube.com/vi/" + recommended_item_id + "/hqdefault.jpg";
+ body_items.push_back(std::move(body_item));
+
+ // We dont want more than 150 recommendations
+ if(body_items.size() == 150)
+ break;
+ }
+
+ std::random_shuffle(body_items.begin(), body_items.end());
+ }
+
Program::Program() :
disp(nullptr),
window(sf::VideoMode(1280, 720), "QuickMedia", sf::Style::Default, sf::ContextSettings(0, 0, 0, 3, 3)),
window_size(1280, 720),
- body(nullptr),
- current_plugin(nullptr),
- current_page(Page::SEARCH_SUGGESTION),
+ current_page(PageType::EXIT),
image_index(0)
{
disp = XOpenDisplay(NULL);
@@ -223,10 +311,6 @@ namespace QuickMedia {
abort();
}
- body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
- related_media_body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
- related_media_body->draw_thumbnails = true;
-
struct sigaction action;
action.sa_handler = sigpipe_handler;
sigemptyset(&action.sa_mask);
@@ -262,69 +346,29 @@ namespace QuickMedia {
} else {
running = false;
}
- if(related_media_body)
- delete related_media_body;
- if(body)
- delete body;
- if(file_manager)
- delete file_manager;
- if(current_plugin && current_plugin != file_manager)
- delete current_plugin;
+ if(matrix)
+ delete matrix;
if(disp)
XCloseDisplay(disp);
}
- static SearchResult search_selected_suggestion(Body *input_body, Body *output_body, Plugin *plugin, std::string &selected_title, std::string &selected_url, bool skip_search) {
- BodyItem *selected_item = input_body->get_selected();
- if(!selected_item)
- return SearchResult::ERR;
-
- selected_title = selected_item->get_title();
- selected_url = selected_item->url;
- if(!skip_search) {
- output_body->clear_items();
- SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, output_body->items);
- output_body->reset_selected();
- return search_result;
- } else {
- return SearchResult::OK;
- }
- }
-
static void usage() {
fprintf(stderr, "usage: QuickMedia <plugin> [--tor] [--no-video] [--use-system-mpv-config] [--dir <directory>] [-p <placeholder-text>]\n");
fprintf(stderr, "OPTIONS:\n");
- fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, nyaa.si, matrix, file-manager or dmenu\n");
+ fprintf(stderr, " plugin The plugin to use. Should be either 4chan, manganelo, mangatown, mangadex, pornhub, youtube, nyaa.si, matrix, file-manager\n");
fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n");
fprintf(stderr, " --tor Use tor. Disabled by default\n");
fprintf(stderr, " --use-system-mpv-config Use system mpv config instead of no config. Disabled by default\n");
fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n");
fprintf(stderr, " --upscale-images-force Upscale manga pages using waifu2x-ncnn-vulkan, no matter what the original image resolution is. Disabled by default\n");
fprintf(stderr, " --dir Set the start directory when using file-manager\n");
- fprintf(stderr, " -p Change the placeholder text for dmenu\n");
fprintf(stderr, "EXAMPLES:\n");
fprintf(stderr, "QuickMedia manganelo\n");
fprintf(stderr, "QuickMedia youtube --tor\n");
- fprintf(stderr, "echo \"hello\\nworld\" | QuickMedia dmenu\n");
}
- static bool is_program_executable_by_name(const char *name) {
- // TODO: Implement for Windows. Windows also uses semicolon instead of colon as a separator
- char *env = getenv("PATH");
- std::unordered_set<std::string> paths;
- string_split(env, ':', [&paths](const char *str, size_t size) {
- paths.insert(std::string(str, size));
- return true;
- });
-
- for(const std::string &path_str : paths) {
- Path path(path_str);
- path.join(name);
- if(get_file_type(path) == FileType::REGULAR)
- return true;
- }
-
- return false;
+ static bool is_manga_plugin(const char *plugin_name) {
+ return strcmp(plugin_name, "manganelo") == 0 || strcmp(plugin_name, "mangatown") == 0 || strcmp(plugin_name, "mangadex") == 0;
}
int Program::run(int argc, char **argv) {
@@ -333,45 +377,40 @@ namespace QuickMedia {
return -1;
}
- current_plugin = nullptr;
std::string plugin_logo_path;
- std::string search_placeholder;
const char *start_dir = nullptr;
+ std::vector<Tab> tabs;
for(int i = 1; i < argc; ++i) {
- if(!current_plugin) {
+ if(!plugin_name) {
if(strcmp(argv[i], "manganelo") == 0) {
- current_plugin = new Manganelo();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/manganelo_logo.png";
} else if(strcmp(argv[i], "mangatown") == 0) {
- current_plugin = new Mangatown();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/mangatown_logo.png";
} else if(strcmp(argv[i], "mangadex") == 0) {
- current_plugin = new Mangadex();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/mangadex_logo.png";
} else if(strcmp(argv[i], "youtube") == 0) {
- current_plugin = new Youtube();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/yt_logo_rgb_dark_small.png";
} else if(strcmp(argv[i], "pornhub") == 0) {
- current_plugin = new Pornhub();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/pornhub_logo.png";
+ plugin_name = argv[i];
} else if(strcmp(argv[i], "4chan") == 0) {
- current_plugin = new Fourchan(resources_root);
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/4chan_logo.png";
} else if(strcmp(argv[i], "nyaa.si") == 0) {
- current_plugin = new NyaaSi();
+ plugin_name = argv[i];
plugin_logo_path = resources_root + "images/nyaa_si_logo.png";
} else if(strcmp(argv[i], "matrix") == 0) {
- current_plugin = new Matrix();
+ plugin_name = argv[i];
+ matrix = new Matrix();
plugin_logo_path = resources_root + "images/matrix_logo.png";
} else if(strcmp(argv[i], "file-manager") == 0) {
- current_plugin = new FileManager();
- } else if(strcmp(argv[i], "dmenu") == 0) {
- current_plugin = new Dmenu();
- } else {
- fprintf(stderr, "Invalid plugin %s\n", argv[i]);
- usage();
- return -1;
+ plugin_name = argv[i];
}
}
@@ -390,11 +429,6 @@ namespace QuickMedia {
start_dir = argv[i + 1];
++i;
}
- } else if(strcmp(argv[i], "-p") == 0) {
- if(i < argc - 1) {
- search_placeholder = argv[i + 1];
- ++i;
- }
} else if(argv[i][0] == '-') {
fprintf(stderr, "Invalid option %s\n", argv[i]);
usage();
@@ -402,43 +436,19 @@ namespace QuickMedia {
}
}
- if(!current_plugin) {
+ if(!plugin_name) {
fprintf(stderr, "Missing plugin argument\n");
usage();
return -1;
}
- if(!search_placeholder.empty() && current_plugin->name == "dmenu") {
- fprintf(stderr, "Option -p is only valid with dmenu\n");
- usage();
- return -1;
- }
-
- if(current_plugin->name == "file-manager") {
- current_page = Page::FILE_MANAGER;
- file_manager = static_cast<FileManager*>(current_plugin);
- } else {
- if(start_dir) {
- fprintf(stderr, "Option --dir is only valid with file-manager\n");
- usage();
- return -1;
- }
- }
-
- if(start_dir) {
- if(!static_cast<FileManager*>(current_plugin)->set_current_directory(start_dir)) {
- fprintf(stderr, "Invalid directory provided with --dir: %s\n", start_dir);
- return -3;
- }
- }
-
if(use_tor && !is_program_executable_by_name("torsocks")) {
fprintf(stderr, "torsocks needs to be installed (and accessible from PATH environment variable) when using the --tor option\n");
return -2;
}
if(upscale_image_action != UpscaleImageAction::NO) {
- if(!current_plugin->is_manga()) {
+ if(!is_manga_plugin(plugin_name)) {
fprintf(stderr, "Option --upscale-images/-upscale-images-force is only valid for manganelo, mangatown and mangadex\n");
return -2;
}
@@ -485,8 +495,13 @@ namespace QuickMedia {
running = true;
}
- current_plugin->use_tor = use_tor;
- window.setTitle("QuickMedia - " + current_plugin->name);
+ if(strcmp(plugin_name, "file-manager") != 0 && start_dir) {
+ fprintf(stderr, "Option --dir is only valid with file-manager\n");
+ usage();
+ return -1;
+ }
+
+ window.setTitle("QuickMedia - " + std::string(plugin_name));
if(!plugin_logo_path.empty()) {
if(!plugin_logo.loadFromFile(plugin_logo_path)) {
@@ -497,99 +512,94 @@ namespace QuickMedia {
plugin_logo.setSmooth(true);
}
- if(current_plugin->name == "matrix") {
- Matrix *matrix = static_cast<Matrix*>(current_plugin);
- if(matrix->load_and_verify_cached_session() == PluginResult::OK) {
- current_page = Page::CHAT;
- } else {
- fprintf(stderr, "Failed to load session cache, redirecting to login page\n");
- current_page = Page::CHAT_LOGIN;
+ if(strcmp(plugin_name, "manganelo") == 0) {
+ auto search_body = create_body();
+ search_body->draw_thumbnails = true;
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 200)});
+
+ auto history_body = create_body();
+ manga_get_watch_history(plugin_name, history_body->items);
+ tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "mangatown") == 0) {
+ auto search_body = create_body();
+ search_body->draw_thumbnails = true;
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<MangatownSearchPage>(this), create_search_bar("Search...", 200)});
+
+ auto history_body = create_body();
+ manga_get_watch_history(plugin_name, history_body->items);
+ tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "mangadex") == 0) {
+ auto search_body = create_body();
+ search_body->draw_thumbnails = true;
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 300)});
+
+ auto history_body = create_body();
+ manga_get_watch_history(plugin_name, history_body->items);
+ tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "nyaa.si") == 0) {
+ auto category_page = std::make_unique<NyaaSiCategoryPage>(this);
+ auto categories_body = create_body();
+ category_page->get_categories(categories_body->items);
+ tabs.push_back(Tab{std::move(categories_body), std::move(category_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "4chan") == 0) {
+ auto boards_page = std::make_unique<FourchanBoardsPage>(this, resources_root);
+ auto boards_body = create_body();
+ boards_page->get_boards(boards_body->items);
+ tabs.push_back(Tab{std::move(boards_body), std::move(boards_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "file-manager") == 0) {
+ auto file_manager_page = std::make_unique<FileManagerPage>(this);
+ if(start_dir && !file_manager_page->set_current_directory(start_dir)) {
+ fprintf(stderr, "Invalid directory provided with --dir: %s\n", start_dir);
+ return -3;
}
+ auto file_manager_body = create_body();
+ file_manager_page->get_files_in_directory(file_manager_body->items);
+ tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "youtube") == 0) {
+ auto search_body = create_body();
+ search_body->draw_thumbnails = true;
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)});
+
+ auto history_body = create_body();
+ history_body->draw_thumbnails = true;
+ youtube_get_watch_history(history_body->items);
+ tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ auto recommended_body = create_body();
+ recommended_body->draw_thumbnails = true;
+ fill_recommended_items_from_json(load_recommended_json(), recommended_body->items);
+ tabs.push_back(Tab{std::move(recommended_body), std::make_unique<RecommendedPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ } else if(strcmp(plugin_name, "pornhub") == 0) {
+ auto search_body = create_body();
+ search_body->draw_thumbnails = true;
+ tabs.push_back(Tab{std::move(search_body), std::make_unique<PornhubSearchPage>(this), create_search_bar("Search...", 500)});
}
- if(search_placeholder.empty())
- search_placeholder = "Search...";
+ if(!tabs.empty()) {
+ page_loop(std::move(tabs));
+ return exit_code;
+ }
- search_bar = std::make_unique<SearchBar>(*font, &plugin_logo, search_placeholder);
- search_bar->text_autosearch_delay = current_plugin->get_search_delay();
+ if(matrix) {
+ matrix->use_tor = use_tor;
+ if(matrix->load_and_verify_cached_session() == PluginResult::OK) {
+ current_page = PageType::CHAT;
+ } else {
+ fprintf(stderr, "Failed to load session cache, redirecting to login page\n");
+ current_page = PageType::CHAT_LOGIN;
+ }
- while(window.isOpen()) {
- switch(current_page) {
- case Page::EXIT:
- window.close();
- break;
- case Page::SEARCH_SUGGESTION:
- body->draw_thumbnails = current_plugin->search_suggestions_has_thumbnails();
- search_suggestion_page();
- body->clear_thumbnails();
- break;
- case Page::VIDEO_CONTENT:
- body->draw_thumbnails = false;
- video_content_page();
- break;
- case Page::EPISODE_LIST:
- body->draw_thumbnails = false;
- episode_list_page();
- body->clear_thumbnails();
- break;
- case Page::IMAGES: {
- body->draw_thumbnails = false;
- window.setKeyRepeatEnabled(false);
- window.setFramerateLimit(20);
- image_page();
- body->filter_search_fuzzy("");
- if(vsync_set)
- window.setFramerateLimit(0);
- else
- window.setFramerateLimit(monitor_hz);
- window.setKeyRepeatEnabled(true);
- break;
- }
- case Page::IMAGES_CONTINUOUS: {
- body->draw_thumbnails = false;
- window.setKeyRepeatEnabled(false);
- image_continuous_page();
- body->filter_search_fuzzy("");
- window.setKeyRepeatEnabled(true);
- break;
- }
- case Page::CONTENT_LIST: {
- body->draw_thumbnails = true;
- content_list_page();
- body->clear_thumbnails();
- break;
- }
- case Page::CONTENT_DETAILS: {
- body->draw_thumbnails = true;
- content_details_page();
- body->clear_thumbnails();
- break;
- }
- case Page::IMAGE_BOARD_THREAD_LIST: {
- body->draw_thumbnails = true;
- image_board_thread_list_page();
- body->clear_thumbnails();
- break;
- }
- case Page::IMAGE_BOARD_THREAD: {
- body->draw_thumbnails = true;
- image_board_thread_page();
- body->clear_thumbnails();
- break;
- }
- case Page::CHAT_LOGIN: {
- chat_login_page();
- break;
- }
- case Page::CHAT: {
- body->draw_thumbnails = true;
- chat_page();
- break;
- }
- case Page::FILE_MANAGER: {
- body->draw_thumbnails = true;
- file_manager_page();
- break;
+ while(window.isOpen()) {
+ switch(current_page) {
+ case PageType::CHAT_LOGIN:
+ chat_login_page();
+ break;
+ case PageType::CHAT:
+ chat_page();
+ break;
+ default:
+ window.close();
+ break;
}
}
}
@@ -597,9 +607,10 @@ namespace QuickMedia {
return exit_code;
}
- void Program::base_event_handler(sf::Event &event, Page previous_page, bool handle_keypress, bool clear_on_escape, bool handle_searchbar) {
+ void Program::base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_keypress, bool handle_searchbar) {
if (event.type == sf::Event::Closed) {
- current_page = Page::EXIT;
+ current_page = PageType::EXIT;
+ window.close();
} else if(event.type == sf::Event::Resized) {
window_size.x = event.size.width;
window_size.y = event.size.height;
@@ -620,13 +631,9 @@ namespace QuickMedia {
body->select_last_item();
} else if(event.key.code == sf::Keyboard::Escape) {
current_page = previous_page;
- if(clear_on_escape) {
- body->clear_items();
- body->reset_selected();
- search_bar->clear();
- }
}
} else if(handle_searchbar) {
+ assert(search_bar);
if(event.type == sf::Event::TextEntered)
search_bar->onTextEntered(event.text.unicode);
search_bar->on_event(event);
@@ -707,76 +714,7 @@ namespace QuickMedia {
}
}
- // TODO: Make asynchronous
- static void fill_recommended_items_from_json(const Json::Value &recommended_json, BodyItems &body_items) {
- assert(recommended_json.isObject());
-
- std::vector<std::pair<std::string, Json::Value>> recommended_items(recommended_json.size());
- /* TODO: Optimize member access */
- for(auto &member_name : recommended_json.getMemberNames()) {
- Json::Value recommended_item = recommended_json[member_name];
- if(recommended_item.isObject())
- recommended_items.push_back(std::make_pair(member_name, std::move(recommended_item)));
- }
-
- /* TODO: Better algorithm for recommendations */
- std::sort(recommended_items.begin(), recommended_items.end(), [](std::pair<std::string, Json::Value> &a, std::pair<std::string, Json::Value> &b) {
- Json::Value &a_timestamp_json = a.second["recommended_timestamp"];
- Json::Value &b_timestamp_json = b.second["recommended_timestamp"];
- int64_t a_timestamp = 0;
- int64_t b_timestamp = 0;
- if(a_timestamp_json.isNumeric())
- a_timestamp = a_timestamp_json.asInt64();
- if(b_timestamp_json.isNumeric())
- b_timestamp = b_timestamp_json.asInt64();
-
- Json::Value &a_recommended_count_json = a.second["recommended_count"];
- Json::Value &b_recommended_count_json = b.second["recommended_count"];
- int64_t a_recommended_count = 0;
- int64_t b_recommended_count = 0;
- if(a_recommended_count_json.isNumeric())
- a_recommended_count = a_recommended_count_json.asInt64();
- if(b_recommended_count_json.isNumeric())
- b_recommended_count = b_recommended_count_json.asInt64();
-
- /* Put frequently recommended videos on top of recommendations. Each recommendation count is worth 5 minutes */
- a_timestamp += (300 * a_recommended_count);
- b_timestamp += (300 * b_recommended_count);
-
- return a_timestamp > b_timestamp;
- });
-
- for(auto it = recommended_items.begin(); it != recommended_items.end(); ++it) {
- const std::string &recommended_item_id = it->first;
- Json::Value &recommended_item = it->second;
-
- int64_t watched_count = 0;
- const Json::Value &watched_count_json = recommended_item["watched_count"];
- if(watched_count_json.isNumeric())
- watched_count = watched_count_json.asInt64();
-
- /* TODO: Improve recommendations with some kind of algorithm. Videos we have seen should be recommended in some cases */
- if(watched_count != 0)
- continue;
-
- const Json::Value &recommended_title_json = recommended_item["title"];
- if(!recommended_title_json.isString())
- continue;
-
- auto body_item = BodyItem::create(recommended_title_json.asString());
- body_item->url = "https://www.youtube.com/watch?v=" + recommended_item_id;
- body_item->thumbnail_url = "https://img.youtube.com/vi/" + recommended_item_id + "/hqdefault.jpg";
- body_items.push_back(std::move(body_item));
-
- // We dont want more than 150 recommendations
- if(body_items.size() == 150)
- break;
- }
-
- std::random_shuffle(body_items.begin(), body_items.end());
- }
-
- static Path get_video_history_filepath(Plugin *plugin) {
+ static Path get_video_history_filepath(const char *plugin_name) {
Path video_history_dir = get_storage_dir().join("history");
if(create_directory_recursive(video_history_dir) != 0) {
std::string err_msg = "Failed to create video history directory ";
@@ -786,10 +724,10 @@ namespace QuickMedia {
}
Path video_history_filepath = video_history_dir;
- return video_history_filepath.join(plugin->name).append(".json");
+ return video_history_filepath.join(plugin_name).append(".json");
}
- static Path get_recommended_filepath(Plugin *plugin) {
+ static Path get_recommended_filepath(const char *plugin_name) {
Path video_history_dir = get_storage_dir().join("recommended");
if(create_directory_recursive(video_history_dir) != 0) {
std::string err_msg = "Failed to create recommended directory ";
@@ -799,13 +737,13 @@ namespace QuickMedia {
}
Path video_history_filepath = video_history_dir;
- return video_history_filepath.join(plugin->name).append(".json");
+ return video_history_filepath.join(plugin_name).append(".json");
}
// This is not cached because we could have multiple instances of QuickMedia running the same plugin!
// TODO: Find a way to optimize this
- Json::Value Program::load_video_history_json(Plugin *plugin) {
- Path video_history_filepath = get_video_history_filepath(plugin);
+ Json::Value Program::load_video_history_json() {
+ Path video_history_filepath = get_video_history_filepath(plugin_name);
Json::Value json_result;
if(!read_file_as_json(video_history_filepath, json_result) || !json_result.isArray())
json_result = Json::Value(Json::arrayValue);
@@ -814,66 +752,62 @@ namespace QuickMedia {
// This is not cached because we could have multiple instances of QuickMedia running the same plugin!
// TODO: Find a way to optimize this
- Json::Value Program::load_recommended_json(Plugin *plugin) {
- Path recommended_filepath = get_recommended_filepath(plugin);
+ Json::Value Program::load_recommended_json() {
+ Path recommended_filepath = get_recommended_filepath(plugin_name);
Json::Value json_result;
if(!read_file_as_json(recommended_filepath, json_result) || !json_result.isObject())
json_result = Json::Value(Json::objectValue);
return json_result;
}
- void Program::plugin_get_watch_history(Plugin *plugin, BodyItems &history_items) {
+ void Program::manga_get_watch_history(const char *plugin_name, BodyItems &history_items) {
// TOOD: Make generic, instead of checking for plugin
- if(plugin->is_manga()) {
- Path content_storage_dir = get_storage_dir().join(plugin->name);
- if(create_directory_recursive(content_storage_dir) != 0) {
- show_notification("Storage", "Failed to create directory: " + content_storage_dir.data, Urgency::CRITICAL);
- exit(1);
- }
- Path credentials_storage_dir = get_storage_dir().join("credentials");
- if(create_directory_recursive(credentials_storage_dir) != 0) {
- show_notification("Storage", "Failed to create directory: " + credentials_storage_dir.data, Urgency::CRITICAL);
- exit(1);
- }
- // TODO: Make asynchronous
- for_files_in_dir_sort_last_modified(content_storage_dir, [&history_items, plugin](const std::filesystem::path &filepath) {
- // This can happen when QuickMedia crashes/is killed while writing to storage.
- // In that case, the storage wont be corrupt but there will be .tmp files.
- // TODO: Remove these .tmp files if they exist during startup
- if(filepath.extension() == ".tmp")
- return true;
-
- Path fullpath(filepath.c_str());
- Json::Value body;
- if(!read_file_as_json(fullpath, body)) {
- fprintf(stderr, "Failed to read json file: %s\n", fullpath.data.c_str());
- return true;
- }
+ Path content_storage_dir = get_storage_dir().join(plugin_name);
+ if(create_directory_recursive(content_storage_dir) != 0) {
+ show_notification("Storage", "Failed to create directory: " + content_storage_dir.data, Urgency::CRITICAL);
+ exit(1);
+ }
+ Path credentials_storage_dir = get_storage_dir().join("credentials");
+ if(create_directory_recursive(credentials_storage_dir) != 0) {
+ show_notification("Storage", "Failed to create directory: " + credentials_storage_dir.data, Urgency::CRITICAL);
+ exit(1);
+ }
+ // TODO: Make asynchronous
+ for_files_in_dir_sort_last_modified(content_storage_dir, [&history_items, plugin_name](const std::filesystem::path &filepath) {
+ // This can happen when QuickMedia crashes/is killed while writing to storage.
+ // In that case, the storage wont be corrupt but there will be .tmp files.
+ // TODO: Remove these .tmp files if they exist during startup
+ if(filepath.extension() == ".tmp")
+ return true;
- auto filename = filepath.filename();
- const Json::Value &manga_name = body["name"];
- if(!filename.empty() && manga_name.isString()) {
- // TODO: Add thumbnail
- auto body_item = BodyItem::create(manga_name.asString());
- if(plugin->name == "manganelo")
- body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string());
- else if(plugin->name == "mangadex")
- body_item->url = "https://mangadex.org/title/" + base64_decode(filename.string());
- else if(plugin->name == "mangatown")
- body_item->url = "https://mangatown.com/manga/" + base64_decode(filename.string());
- else
- fprintf(stderr, "Error: Not implemented: filename to manga chapter list\n");
- history_items.push_back(std::move(body_item));
- }
+ Path fullpath(filepath.c_str());
+ Json::Value body;
+ if(!read_file_as_json(fullpath, body) || !body.isObject()) {
+ fprintf(stderr, "Failed to read json file: %s\n", fullpath.data.c_str());
return true;
- });
- return;
- }
+ }
- if(plugin->name != "youtube")
- return;
+ auto filename = filepath.filename();
+ const Json::Value &manga_name = body["name"];
+ if(!filename.empty() && manga_name.isString()) {
+ // TODO: Add thumbnail
+ auto body_item = BodyItem::create(manga_name.asString());
+ if(strcmp(plugin_name, "manganelo") == 0)
+ body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string());
+ else if(strcmp(plugin_name, "mangadex") == 0)
+ body_item->url = "https://mangadex.org/title/" + base64_decode(filename.string());
+ else if(strcmp(plugin_name, "mangatown") == 0)
+ body_item->url = "https://mangatown.com/manga/" + base64_decode(filename.string());
+ else
+ fprintf(stderr, "Error: Not implemented: filename to manga chapter list\n");
+ history_items.push_back(std::move(body_item));
+ }
+ return true;
+ });
+ }
- fill_history_items_from_json(load_video_history_json(plugin), history_items);
+ void Program::youtube_get_watch_history(BodyItems &history_items) {
+ fill_history_items_from_json(load_video_history_json(), history_items);
}
static void get_body_dimensions(const sf::Vector2f &window_size, SearchBar *search_bar, sf::Vector2f &body_pos, sf::Vector2f &body_size, bool has_tabs = false) {
@@ -890,7 +824,7 @@ namespace QuickMedia {
if(!has_tabs)
tab_h = 0.0f;
- float search_bottom = search_bar->getBottomWithoutShadow();
+ float search_bottom = search_bar ? search_bar->getBottomWithoutShadow() : 0.0f;
body_pos = sf::Vector2f(body_padding_horizontal, search_bottom + body_padding_vertical + tab_h);
body_size = sf::Vector2f(body_width, window_size.y - search_bottom - body_padding_vertical - tab_h);
}
@@ -922,173 +856,189 @@ namespace QuickMedia {
std::unique_ptr<SearchBar> password;
};
- struct Tab {
- Body *body;
- std::unique_ptr<LoginTab> login_tab;
- SearchSuggestionTab tab;
- sf::Text *text;
- };
-
- bool Program::on_search_suggestion_submit_text(Body *input_body, Body *output_body) {
- if(input_body->no_items_visible())
- return false;
-
- Page next_page = current_plugin->get_page_after_search();
- bool skip_search = (next_page == Page::VIDEO_CONTENT || next_page == Page::CONTENT_LIST);
- // TODO: This shouldn't be done if search_selected_suggestion fails
- if(search_selected_suggestion(input_body, output_body, current_plugin, content_title, content_url, skip_search) != SearchResult::OK) {
- show_notification("Search", "Search failed!", Urgency::CRITICAL);
- return false;
- }
+ bool Program::is_tor_enabled() {
+ return use_tor;
+ }
- if(next_page == Page::EPISODE_LIST && current_plugin->is_manga()) {
- Manga *manga_plugin = static_cast<Manga*>(current_plugin);
- if(content_url.empty()) {
- show_notification("Manga", "Url is missing for manga!", Urgency::CRITICAL);
- return false;
- }
-
- Path content_storage_dir = get_storage_dir().join(current_plugin->name);
+ std::unique_ptr<Body> Program::create_body() {
+ return std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
+ }
- std::string manga_id;
- if(!manga_plugin->extract_id_from_url(content_url, manga_id))
- return false;
+ std::unique_ptr<SearchBar> Program::create_search_bar(const std::string &placeholder, int search_delay) {
+ auto search_bar = std::make_unique<SearchBar>(*font, &plugin_logo, placeholder);
+ search_bar->text_autosearch_delay = search_delay;
+ return search_bar;
+ }
- manga_id_base64 = base64_encode(manga_id);
- content_storage_file = content_storage_dir.join(manga_id_base64);
- content_storage_json.clear();
- content_storage_json["name"] = content_title;
- FileType file_type = get_file_type(content_storage_file);
- if(file_type == FileType::REGULAR)
- read_file_as_json(content_storage_file, content_storage_json);
- } else if(next_page == Page::VIDEO_CONTENT) {
- watched_videos.clear();
- if(content_url.empty())
- next_page = Page::SEARCH_SUGGESTION;
- else {
- page_stack.push(Page::SEARCH_SUGGESTION);
- }
- current_page = next_page;
+ bool Program::load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_id) {
+ Path content_storage_dir = get_storage_dir().join(service_name);
+ manga_id_base64 = base64_encode(manga_id);
+ content_storage_file = content_storage_dir.join(manga_id_base64);
+ content_storage_json.clear();
+ content_storage_json["name"] = manga_title;
+ FileType file_type = get_file_type(content_storage_file);
+ if(file_type == FileType::REGULAR) {
+ if(read_file_as_json(content_storage_file, content_storage_json) && content_storage_json.isObject())
+ return true;
return false;
- } else if(next_page == Page::CONTENT_LIST) {
- content_list_url = content_url;
- } else if(next_page == Page::IMAGE_BOARD_THREAD_LIST) {
- image_board_thread_list_url = content_url;
+ } else {
+ return true;
}
- current_page = next_page;
- return true;
}
- void Program::search_suggestion_page() {
- std::string update_search_text;
- bool search_text_updated = false;
- bool search_running = false;
- bool typing = false;
- bool is_fourchan = current_plugin->name == "4chan";
-
- std::string autocomplete_text;
- bool autocomplete_running = false;
-
- Body history_body(this, font.get(), bold_font.get(), cjk_font.get());
- std::unique_ptr<Body> recommended_body;
- sf::Text all_tab_text("All", *font, tab_text_size);
- sf::Text history_tab_text("History", *font, tab_text_size);
- sf::Text recommended_tab_text("Recommended", *font, tab_text_size);
- sf::Text login_tab_text("Login", *font, tab_text_size);
- SearchBar *focused_login_input = nullptr;
-
- if(current_plugin->name == "youtube") {
- recommended_body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get());
- recommended_body->draw_thumbnails = true;
- fill_recommended_items_from_json(load_recommended_json(current_plugin), recommended_body->items);
+ void Program::select_file(const std::string &filepath) {
+ puts(filepath.c_str());
+ selected_files.push_back(filepath);
+ }
+
+ void Program::page_loop(std::vector<Tab> tabs) {
+ if(tabs.empty()) {
+ show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL);
+ return;
}
- std::vector<Tab> tabs;
- int selected_tab = 0;
+ const Json::Value *json_chapters = &Json::Value::nullSingleton();
+ if(content_storage_json.isObject()) {
+ const Json::Value &chapters_json = content_storage_json["chapters"];
+ if(chapters_json.isObject())
+ json_chapters = &chapters_json;
+ }
- auto login_submit_callback = [this, &tabs, &selected_tab](const std::string&) -> bool {
- if(!tabs[selected_tab].body) {
- std::string username = tabs[selected_tab].login_tab->username->get_text();
- std::string password = tabs[selected_tab].login_tab->password->get_text();
- if(current_plugin->name == "4chan") {
- std::string response_msg;
- PluginResult result = static_cast<Fourchan*>(current_plugin)->login(username, password, response_msg);
- if(result == PluginResult::NET_ERR) {
- show_notification("4chan", "Login failed!", Urgency::CRITICAL);
- } else if(result == PluginResult::ERR) {
- std::string desc = "Login failed, reason: ";
- if(response_msg.empty())
- desc += "Unknown";
- else
- desc += response_msg;
- show_notification("4chan", desc, Urgency::CRITICAL);
- } else if(result == PluginResult::OK) {
- show_notification("4chan", "Successfully logged in!", Urgency::LOW);
- selected_tab = 0;
- }
- }
- }
- return false;
+ struct TabAssociatedData {
+ std::string update_search_text;
+ bool search_text_updated = false;
+ bool search_running = false;
+ bool typing = false;
+ bool fetching_next_page_running = false;
+ int fetched_page = 0;
+ sf::Text search_result_text;
+ std::future<BodyItems> search_future;
+ std::future<BodyItems> next_page_future;
};
- tabs.push_back(Tab{body, nullptr, SearchSuggestionTab::ALL, &all_tab_text});
- tabs.push_back(Tab{&history_body, nullptr, SearchSuggestionTab::HISTORY, &history_tab_text});
- if(recommended_body)
- tabs.push_back(Tab{recommended_body.get(), nullptr, SearchSuggestionTab::RECOMMENDED, &recommended_tab_text});
- if(is_fourchan) {
- tabs.push_back(Tab{nullptr, std::make_unique<LoginTab>(*font), SearchSuggestionTab::LOGIN, &login_tab_text});
- focused_login_input = tabs.back().login_tab->username.get();
+ std::vector<TabAssociatedData> tab_associated_data;
+ for(size_t i = 0; i < tabs.size(); ++i) {
+ TabAssociatedData data;
+ data.search_result_text = sf::Text("", *font, 30);
+ tab_associated_data.push_back(std::move(data));
+ }
- tabs.back().login_tab->username->caret_visible = true;
- tabs.back().login_tab->password->caret_visible = false;
+ //std::string autocomplete_text;
+ //bool autocomplete_running = false;
- tabs.back().login_tab->username->onTextSubmitCallback = login_submit_callback;
- tabs.back().login_tab->password->onTextSubmitCallback = login_submit_callback;
- }
+ double gradient_inc = 0.0;
+ const float gradient_height = 5.0f;
+ sf::Vertex gradient_points[4];
- plugin_get_watch_history(current_plugin, history_body.items);
- if(current_plugin->name == "youtube")
- history_body.draw_thumbnails = true;
+ sf::Text tab_text("", *font, tab_text_size);
+ int selected_tab = 0;
- search_bar->onTextBeginTypingCallback = [&typing]() {
- typing = true;
- };
+ bool loop_running = true;
- search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay();
- search_bar->onAutocompleteRequestCallback = [this, &tabs, &selected_tab, &autocomplete_text](const std::string &text) {
- if(tabs[selected_tab].body == body && !current_plugin->search_is_filter())
- autocomplete_text = text;
- };
+ auto submit_handler = [this, &tabs, &selected_tab, &loop_running]() {
+ BodyItem *selected_item = tabs[selected_tab].body->get_selected();
+ if(!selected_item)
+ return;
+
+ std::vector<Tab> new_tabs;
+ PluginResult submit_result = tabs[selected_tab].page->submit(selected_item->get_title(), selected_item->url, new_tabs);
+ if(submit_result == PluginResult::OK) {
+ if(tabs[selected_tab].page->is_single_page()) {
+ tabs[selected_tab].search_bar->clear();
+ if(new_tabs.size() == 1)
+ tabs[selected_tab].body = std::move(new_tabs[0].body);
+ else
+ loop_running = false;
+ return;
+ }
+
+ if(new_tabs.empty())
+ return;
- std::string recommended_filter;
+ for(Tab &tab : tabs) {
+ tab.body->clear_cache();
+ }
- search_bar->onTextUpdateCallback = [&update_search_text, &search_text_updated, this, &tabs, &selected_tab, &typing, &recommended_body, &recommended_filter](const std::string &text) {
- if(tabs[selected_tab].body == body && !current_plugin->search_is_filter()) {
- update_search_text = text;
- search_text_updated = true;
+ if(new_tabs.size() == 1 && new_tabs[0].page->is_manga_images_page()) {
+ select_episode(selected_item, false);
+ Body *chapters_body = tabs[selected_tab].body.get();
+ chapters_body->filter_search_fuzzy(""); // Needed (or not really) to go to the next chapter when reaching the last page of a chapter
+ MangaImagesPage *manga_images_page = static_cast<MangaImagesPage*>(new_tabs[0].page.get());
+ window.setKeyRepeatEnabled(false);
+ while(true) {
+ if(current_page == PageType::IMAGES) {
+ window.setFramerateLimit(20);
+ while(current_page == PageType::IMAGES) {
+ int page_navigation = image_page(manga_images_page, chapters_body);
+ if(page_navigation == -1) {
+ // TODO: Make this work if the list is sorted differently than from newest to oldest.
+ chapters_body->select_next_item();
+ select_episode(chapters_body->get_selected(), true);
+ image_index = 99999; // Start at the page that shows we are at the end of the chapter
+ manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url);
+ } else if(page_navigation == 1) {
+ // TODO: Make this work if the list is sorted differently than from newest to oldest.
+ chapters_body->select_previous_item();
+ select_episode(chapters_body->get_selected(), true);
+ manga_images_page->change_chapter(chapters_body->get_selected()->get_title(), chapters_body->get_selected()->url);
+ }
+ }
+ if(vsync_set)
+ window.setFramerateLimit(0);
+ else
+ window.setFramerateLimit(monitor_hz);
+ } else if(current_page == PageType::IMAGES_CONTINUOUS) {
+ image_continuous_page(manga_images_page);
+ } else {
+ break;
+ }
+ }
+ window.setKeyRepeatEnabled(true);
+ } else if(new_tabs.size() == 1 && new_tabs[0].page->is_image_board_thread_page()) {
+ current_page = PageType::IMAGE_BOARD_THREAD;
+ image_board_thread_page(static_cast<ImageBoardThreadPage*>(new_tabs[0].page.get()), new_tabs[0].body.get());
+ } else if(new_tabs.size() == 1 && new_tabs[0].page->is_video_page()) {
+ current_page = PageType::VIDEO_CONTENT;
+ video_content_page(new_tabs[0].page.get(), selected_item->url, selected_item->get_title());
+ } else {
+ page_loop(std::move(new_tabs));
+ }
} else {
- tabs[selected_tab].body->filter_search_fuzzy(text);
- tabs[selected_tab].body->select_first_item();
+ // TODO: Show the exact cause of error (get error message from curl).
+ // TODO: Make asynchronous
+ show_notification("QuickMedia", std::string("Submit failed for page ") + tabs[selected_tab].page->get_title(), Urgency::CRITICAL);
}
- if(tabs[selected_tab].body == recommended_body.get())
- recommended_filter = text;
- typing = false;
};
- search_bar->onTextSubmitCallback = [this, &tabs, &selected_tab, &typing](const std::string&) -> bool {
- if(current_plugin->name != "dmenu") {
- if(typing || tabs[selected_tab].body->no_items_visible())
- return false;
- }
- return on_search_suggestion_submit_text(tabs[selected_tab].body, body);
- };
+ for(size_t i = 0; i < tabs.size(); ++i) {
+ Tab &tab = tabs[i];
+ TabAssociatedData &associated_data = tab_associated_data[i];
+ if(!tab.search_bar)
+ continue;
- if(current_plugin->get_front_page(body->items) != PluginResult::OK) {
- show_notification("QuickMedia", "Failed to get front page", Urgency::CRITICAL);
- current_page = Page::EXIT;
- return;
+ // tab.search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay();
+ // tab.search_bar->onAutocompleteRequestCallback = [this, &tabs, &selected_tab, &autocomplete_text](const std::string &text) {
+ // if(tabs[selected_tab].body == body && !current_plugin->search_is_filter())
+ // autocomplete_text = text;
+ // };
+
+ tab.search_bar->onTextUpdateCallback = [&associated_data, &tabs, i](const std::string &text) {
+ if(!tabs[i].page->search_is_filter()) {
+ associated_data.update_search_text = text;
+ associated_data.search_text_updated = true;
+ } else {
+ tabs[i].body->filter_search_fuzzy(text);
+ tabs[i].body->select_first_item();
+ }
+ associated_data.typing = false;
+ };
+
+ tab.search_bar->onTextSubmitCallback = [&submit_handler, &associated_data](const std::string&) {
+ if(associated_data.typing)
+ return;
+ submit_handler();
+ };
}
- body->clamp_selection();
sf::Vector2f body_pos;
sf::Vector2f body_size;
@@ -1103,149 +1053,247 @@ namespace QuickMedia {
sf::RoundedRectangleShape tab_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10);
tab_background.setFillColor(tab_selected_color);
- while (current_page == Page::SEARCH_SUGGESTION) {
+ sf::Clock frame_timer;
+
+ while (window.isOpen() && loop_running) {
+ sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
+
while (window.pollEvent(event)) {
- base_event_handler(event, Page::EXIT, false, true, tabs[selected_tab].body != nullptr);
+ if (event.type == sf::Event::Closed) {
+ window.close();
+ } else if(event.type == sf::Event::Resized) {
+ window_size.x = event.size.width;
+ window_size.y = event.size.height;
+ sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
+ window.setView(sf::View(visible_area));
+ }
+
+ if(tabs[selected_tab].search_bar) {
+ if(event.type == sf::Event::TextEntered)
+ tabs[selected_tab].search_bar->onTextEntered(event.text.unicode);
+ tabs[selected_tab].search_bar->on_event(event);
+ }
+
if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus)
redraw = true;
else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::Up) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_previous_item();
- } else if(event.key.code == sf::Keyboard::Down) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_next_item();
+ if(event.key.code == sf::Keyboard::Down || event.key.code == sf::Keyboard::PageDown || event.key.code == sf::Keyboard::End) {
+ bool hit_bottom = false;
+ switch(event.key.code) {
+ case sf::Keyboard::Down:
+ hit_bottom = !tabs[selected_tab].body->select_next_item();
+ break;
+ case sf::Keyboard::PageDown:
+ hit_bottom = !tabs[selected_tab].body->select_next_page();
+ break;
+ case sf::Keyboard::End:
+ tabs[selected_tab].body->select_last_item();
+ hit_bottom = true;
+ break;
+ default:
+ hit_bottom = false;
+ break;
+ }
+ if(hit_bottom && !tab_associated_data[selected_tab].search_running && !tab_associated_data[selected_tab].fetching_next_page_running && tabs[selected_tab].page) {
+ gradient_inc = 0.0;
+ tab_associated_data[selected_tab].fetching_next_page_running = true;
+ int next_page = tab_associated_data[selected_tab].fetched_page + 1;
+ Page *page = tabs[selected_tab].page.get();
+ std::string update_search_text = tab_associated_data[selected_tab].update_search_text;
+ tab_associated_data[selected_tab].next_page_future = std::async(std::launch::async, [update_search_text, next_page, page]() {
+ BodyItems result_items;
+ if(page->get_page(update_search_text, next_page, result_items) != PluginResult::OK)
+ fprintf(stderr, "Failed to get next page (page %d)\n", next_page);
+ return result_items;
+ });
+ }
+ } else if(event.key.code == sf::Keyboard::Up) {
+ tabs[selected_tab].body->select_previous_item();
} else if(event.key.code == sf::Keyboard::PageUp) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_previous_page();
- } else if(event.key.code == sf::Keyboard::PageDown) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_next_page();
+ tabs[selected_tab].body->select_previous_page();
} else if(event.key.code == sf::Keyboard::Home) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_first_item();
- } else if(event.key.code == sf::Keyboard::End) {
- if(tabs[selected_tab].body) tabs[selected_tab].body->select_last_item();
+ tabs[selected_tab].body->select_first_item();
} else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::EXIT;
- exit_code = 1;
+ goto page_end;
} else if(event.key.code == sf::Keyboard::Left) {
- if(tabs[selected_tab].body) {
- tabs[selected_tab].body->filter_search_fuzzy("");
- tabs[selected_tab].body->select_first_item();
- tabs[selected_tab].body->clear_thumbnails();
+ if(selected_tab > 0) {
+ tabs[selected_tab].body->clear_cache();
+ --selected_tab;
+ redraw = true;
}
- selected_tab = std::max(0, selected_tab - 1);
- search_bar->clear();
} else if(event.key.code == sf::Keyboard::Right) {
- if(tabs[selected_tab].body) {
- tabs[selected_tab].body->filter_search_fuzzy("");
- tabs[selected_tab].body->select_first_item();
- tabs[selected_tab].body->clear_thumbnails();
+ if(selected_tab < (int)tabs.size() - 1) {
+ tabs[selected_tab].body->clear_cache();
+ ++selected_tab;
+ redraw = true;
}
- selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1);
- search_bar->clear();
} else if(event.key.code == sf::Keyboard::Tab) {
- if(tabs[selected_tab].body) search_bar->set_to_autocomplete();
- }
- }
-
- if(!tabs[selected_tab].body) {
- if(event.type == sf::Event::TextEntered)
- focused_login_input->onTextEntered(event.text.unicode);
- focused_login_input->on_event(event);
-
- if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Tab) {
- focused_login_input->caret_visible = false;
- if(focused_login_input == tabs[selected_tab].login_tab->username.get())
- focused_login_input = tabs[selected_tab].login_tab->password.get();
- else
- focused_login_input = tabs[selected_tab].login_tab->username.get();
- focused_login_input->caret_visible = true;
+ if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete();
+ } else if(event.key.code == sf::Keyboard::Enter) {
+ if(!tabs[selected_tab].search_bar) submit_handler();
+ } else if(event.key.code == sf::Keyboard::T && event.key.control) {
+ BodyItem *selected_item = tabs[selected_tab].body->get_selected();
+ if(selected_item && tabs[selected_tab].page && tabs[selected_tab].page->is_trackable()) {
+ TrackablePage *trackable_page = static_cast<TrackablePage*>(tabs[selected_tab].page.get());
+ TrackResult track_result = trackable_page->track(selected_item->get_title());
+ // TODO: Show proper error message when this fails. For example if we are already tracking the manga
+ if(track_result == TrackResult::OK) {
+ show_notification("Media tracker", "You are now tracking \"" + trackable_page->content_title + "\" after \"" + selected_item->get_title() + "\"", Urgency::LOW);
+ } else {
+ show_notification("Media tracker", "Failed to track media \"" + trackable_page->content_title + "\", chapter: \"" + selected_item->get_title() + "\"", Urgency::CRITICAL);
+ }
+ }
}
}
}
if(redraw) {
redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size, true);
+ if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->onWindowResize(window_size);
+ // TODO: Dont show tabs if there is only one tab
+ get_body_dimensions(window_size, tabs[selected_tab].search_bar.get(), body_pos, body_size, true);
+
+ gradient_points[0].position.x = 0.0f;
+ gradient_points[0].position.y = window_size.y - gradient_height;
+
+ gradient_points[1].position.x = window_size.x;
+ gradient_points[1].position.y = window_size.y - gradient_height;
+
+ gradient_points[2].position.x = window_size.x;
+ gradient_points[2].position.y = window_size.y;
+
+ gradient_points[3].position.x = 0.0f;
+ gradient_points[3].position.y = window_size.y;
}
- if(tabs[selected_tab].body)
- search_bar->update();
+ if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->update();
- if(search_text_updated && !search_running) {
- search_suggestion_future = std::async(std::launch::async, [this, update_search_text]() {
- BodyItems result;
- if(current_plugin->update_search_suggestions(update_search_text, result) != SuggestionResult::OK) {
- show_notification("Search", "Search failed!", Urgency::CRITICAL);
+ for(size_t i = 0; i < tabs.size(); ++i) {
+ TabAssociatedData &associated_data = tab_associated_data[i];
+
+ if(associated_data.fetching_next_page_running && associated_data.next_page_future.valid() && associated_data.next_page_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ BodyItems new_body_items = associated_data.next_page_future.get();
+ fprintf(stderr, "Finished fetching page %d, num new messages: %zu\n", associated_data.fetched_page + 1, new_body_items.size());
+ size_t num_new_messages = new_body_items.size();
+ if(num_new_messages > 0) {
+ tabs[i].body->append_items(std::move(new_body_items));
+ associated_data.fetched_page++;
}
- return result;
- });
- update_search_text.clear();
- search_text_updated = false;
- search_running = true;
- }
+ associated_data.fetching_next_page_running = false;
+ }
- if(search_running && search_suggestion_future.valid() && search_suggestion_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- if(!search_text_updated) {
- body->items = search_suggestion_future.get();
- body->select_first_item();
- } else {
- search_suggestion_future.get();
+ if(associated_data.search_text_updated && !associated_data.search_running && !associated_data.fetching_next_page_running) {
+ Page *page = tabs[i].page.get();
+ std::string update_search_text = associated_data.update_search_text;
+ associated_data.search_future = std::async(std::launch::async, [update_search_text, page]() {
+ BodyItems result_items;
+ if(page->search(update_search_text, result_items) != SearchResult::OK) {
+ show_notification("QuickMedia", "Search failed!", Urgency::CRITICAL);
+ }
+ return result_items;
+ });
+ update_search_text.clear();
+ associated_data.search_text_updated = false;
+ associated_data.search_running = true;
+ associated_data.search_result_text.setString("Searching...");
+ }
+
+ if(associated_data.search_running && associated_data.search_future.valid() && associated_data.search_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ if(!associated_data.search_text_updated) {
+ BodyItems result_items = associated_data.search_future.get();
+ tabs[i].body->items = std::move(result_items);
+ tabs[i].body->select_first_item();
+ if(tabs[i].body->items.empty())
+ associated_data.search_result_text.setString("No results found");
+ else
+ associated_data.search_result_text.setString("");
+ } else {
+ associated_data.search_future.get();
+ }
+ associated_data.search_running = false;
}
- search_running = false;
}
- if(!autocomplete_text.empty() && !autocomplete_running) {
- autocomplete_future = std::async(std::launch::async, [this, autocomplete_text]() {
- return current_plugin->autocomplete_search(autocomplete_text);
- });
- autocomplete_text.clear();
- autocomplete_running = true;
- }
+ // if(!autocomplete_text.empty() && !autocomplete_running) {
+ // autocomplete_future = std::async(std::launch::async, [this, autocomplete_text]() {
+ // return current_plugin->autocomplete_search(autocomplete_text);
+ // });
+ // autocomplete_text.clear();
+ // autocomplete_running = true;
+ // }
- if(autocomplete_running && autocomplete_future.valid() && autocomplete_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- search_bar->set_autocomplete_text(autocomplete_future.get());
- autocomplete_running = false;
- }
+ // if(autocomplete_running && autocomplete_future.valid() && autocomplete_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
+ // if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_autocomplete_text(autocomplete_future.get());
+ // autocomplete_running = false;
+ // }
window.clear(back_color);
- if(tabs[selected_tab].body)
- search_bar->draw(window, false);
+ if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, false);
+
{
+ float shade_extra_height = 0.0f;
+ if(!tabs[selected_tab].search_bar)
+ shade_extra_height = 10.0f;
+
const float width_per_tab = window_size.x / tabs.size();
tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height));
- float tab_vertical_offset = search_bar->getBottomWithoutShadow();
- if(tabs[selected_tab].body) {
- tabs[selected_tab].body->draw(window, body_pos, body_size);
- } else {
- tabs[selected_tab].login_tab->username->draw(window, false);
- tabs[selected_tab].login_tab->password->draw(window, false);
- tabs[selected_tab].login_tab->password->set_vertical_position(tabs[selected_tab].login_tab->username->getBottomWithoutShadow());
- tab_vertical_offset = tabs[selected_tab].login_tab->username->getBottomWithoutShadow() + tabs[selected_tab].login_tab->password->getBottomWithoutShadow();
- }
- const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f);
+ float tab_vertical_offset = tabs[selected_tab].search_bar ? tabs[selected_tab].search_bar->getBottomWithoutShadow() : 0.0f;
+ tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters);
+ const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f) + shade_extra_height;
tab_shade.setPosition(0.0f, tab_spacer_height + std::floor(tab_vertical_offset));
- tab_shade.setSize(sf::Vector2f(window_size.x, tab_height + 10.0f));
+ tab_shade.setSize(sf::Vector2f(window_size.x, shade_extra_height + tab_height + 10.0f));
window.draw(tab_shade);
int i = 0;
+ // TODO: Dont show tabs if there is only one tab
for(Tab &tab : tabs) {
if(i == selected_tab) {
- tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset));
+ tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset) + shade_extra_height);
window.draw(tab_background);
}
const float center = (i * width_per_tab) + (width_per_tab * 0.5f);
- tab.text->setPosition(std::floor(center - tab.text->getLocalBounds().width * 0.5f), tab_y);
- window.draw(*tab.text);
+ // TODO: Optimize. Only set once for each tab!
+ tab_text.setString(tab.page->get_title());
+ tab_text.setPosition(std::floor(center - tab_text.getLocalBounds().width * 0.5f), tab_y);
+ window.draw(tab_text);
++i;
}
}
+ if(tab_associated_data[selected_tab].fetching_next_page_running) {
+ double progress = 0.5 + std::sin(std::fmod(gradient_inc, 360.0) * 0.017453292519943295 - 1.5707963267948966*0.5) * 0.5;
+ gradient_inc += (frame_time_ms * 0.5);
+ sf::Color bottom_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress);
+
+ gradient_points[0].color = back_color;
+ gradient_points[1].color = back_color;
+ gradient_points[2].color = bottom_color;
+ gradient_points[3].color = bottom_color;
+ window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl
+ }
+
+ if(!tab_associated_data[selected_tab].search_result_text.getString().isEmpty()) {
+ auto search_result_text_bounds = tab_associated_data[selected_tab].search_result_text.getLocalBounds();
+ tab_associated_data[selected_tab].search_result_text.setPosition(
+ std::floor(body_pos.x + body_size.x * 0.5f - search_result_text_bounds.width * 0.5f),
+ std::floor(body_pos.y + body_size.y * 0.5f - search_result_text_bounds.height * 0.5f));
+ window.draw(tab_associated_data[selected_tab].search_result_text);
+ }
+
window.display();
}
- search_bar->onTextBeginTypingCallback = nullptr;
- search_bar->onAutocompleteRequestCallback = nullptr;
+ page_end:
+ // TODO: This is needed, because you cant terminate futures without causing an exception to be thrown and its not safe anyways.
+ // Need a way to solve this, we dont want to wait for a search to finish when navigating backwards
+ for(TabAssociatedData &associated_data : tab_associated_data) {
+ if(associated_data.next_page_future.valid())
+ associated_data.next_page_future.get();
+ if(associated_data.search_future.valid())
+ associated_data.search_future.get();
+ }
}
static bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) {
@@ -1326,17 +1374,17 @@ namespace QuickMedia {
return true;
}
- void Program::save_recommendations_from_related_videos() {
+ void Program::save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const Body *related_media_body) {
std::string video_id;
- if(!youtube_url_extract_id(content_url, video_id)) {
+ if(!youtube_url_extract_id(video_url, video_id)) {
std::string err_msg = "Failed to extract id of youtube url ";
- err_msg += content_url;
+ err_msg += video_url;
err_msg + ", video wont be saved in recommendations";
show_notification("Video player", err_msg.c_str(), Urgency::LOW);
return;
}
- Json::Value recommended_json = load_recommended_json(current_plugin);
+ Json::Value recommended_json = load_recommended_json();
time_t time_now = time(NULL);
Json::Value &existing_recommended_json = recommended_json[video_id];
@@ -1349,7 +1397,7 @@ namespace QuickMedia {
existing_recommended_json["watched_timestamp"] = time_now;
} else {
Json::Value new_content_object(Json::objectValue);
- new_content_object["title"] = content_title;
+ new_content_object["title"] = video_title;
new_content_object["recommended_timestamp"] = time_now;
new_content_object["recommended_count"] = 1;
new_content_object["watched_count"] = 1;
@@ -1381,24 +1429,21 @@ namespace QuickMedia {
break;
}
} else {
- fprintf(stderr, "Failed to extract id of youtube url %s, video wont be saved in recommendations\n", content_url.c_str());
+ fprintf(stderr, "Failed to extract id of youtube url %s, video wont be saved in recommendations\n", video_url.c_str());
}
}
- save_json_to_file_atomic(get_recommended_filepath(current_plugin), recommended_json);
+ save_json_to_file_atomic(get_recommended_filepath(plugin_name), recommended_json);
}
#define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask))
- void Program::video_content_page() {
- search_bar->onTextUpdateCallback = nullptr;
- search_bar->onTextSubmitCallback = nullptr;
-
+ void Program::video_content_page(Page *page, std::string video_url, std::string video_title) {
sf::Clock time_watched_timer;
bool added_recommendations = false;
bool video_loaded = false;
- Page previous_page = pop_page_stack();
+ PageType previous_page = pop_page_stack();
std::unique_ptr<VideoPlayer> video_player;
std::unique_ptr<sf::RenderWindow> related_media_window;
@@ -1408,56 +1453,45 @@ namespace QuickMedia {
sf::Text related_videos_text("Related videos", *bold_font, 20);
const float related_videos_text_height = related_videos_text.getCharacterSize();
+ auto related_media_body = create_body();
+ related_media_body->draw_thumbnails = true;
+
sf::WindowHandle video_player_window = None;
- auto on_window_create = [this, &video_player_window, &related_media_window, &related_media_window_size](sf::WindowHandle _video_player_window) mutable {
+ auto on_window_create = [this, &video_player_window](sf::WindowHandle _video_player_window) mutable {
video_player_window = _video_player_window;
-
- if(!current_plugin->is_image_board()) {
- related_media_window_size.x = window_size.x * RELATED_MEDIA_WINDOW_WIDTH;
- related_media_window_size.y = window_size.y;
- related_media_window = std::make_unique<sf::RenderWindow>(sf::VideoMode(related_media_window_size.x, related_media_window_size.y), "", 0, sf::ContextSettings(0, 0, 0, 3, 3));
- related_media_window->setFramerateLimit(0);
- if(!enable_vsync(disp, related_media_window->getSystemHandle())) {
- fprintf(stderr, "Failed to enable vsync, fallback to frame limiting\n");
- related_media_window->setFramerateLimit(monitor_hz);
- }
- related_media_window->setVisible(false);
- XReparentWindow(disp, related_media_window->getSystemHandle(), video_player_window, window_size.x - related_media_window_size.x, 0);
- }
-
XSelectInput(disp, video_player_window, KeyPressMask | PointerMotionMask);
XSync(disp, False);
};
- auto load_video_error_check = [this, &video_player, previous_page, &time_watched_timer, &added_recommendations]() mutable {
+ auto load_video_error_check = [this, &related_media_body, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &added_recommendations, page]() mutable {
time_watched_timer.restart();
added_recommendations = false;
- watched_videos.insert(content_url);
- VideoPlayer::Error err = video_player->load_video(content_url.c_str(), window.getSystemHandle(), current_plugin->name);
+ watched_videos.insert(video_url);
+ VideoPlayer::Error err = video_player->load_video(video_url.c_str(), window.getSystemHandle(), plugin_name);
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
- err_msg += content_url;
+ err_msg += video_url;
show_notification("Video player", err_msg.c_str(), Urgency::CRITICAL);
current_page = previous_page;
} else {
related_media_body->clear_items();
related_media_body->clear_thumbnails();
- related_media_body->items = current_plugin->get_related_media(content_url);
+ related_media_body->items = page->get_related_media(video_url);
// TODO: Make this also work for other video plugins
- if(current_plugin->name != "youtube")
+ if(strcmp(plugin_name, "youtube") != 0)
return;
std::string video_id;
- if(!youtube_url_extract_id(content_url, video_id)) {
+ if(!youtube_url_extract_id(video_url, video_id)) {
std::string err_msg = "Failed to extract id of youtube url ";
- err_msg += content_url;
+ err_msg += video_url;
err_msg + ", video wont be saved in history";
show_notification("Video player", err_msg.c_str(), Urgency::LOW);
return;
}
- Json::Value video_history_json = load_video_history_json(current_plugin);
+ Json::Value video_history_json = load_video_history_json();
int existing_index = watch_history_get_item_by_id(video_history_json, video_id.c_str());
if(existing_index != -1) {
@@ -1470,18 +1504,18 @@ namespace QuickMedia {
Json::Value new_content_object(Json::objectValue);
new_content_object["id"] = video_id;
- new_content_object["title"] = content_title;
+ new_content_object["title"] = video_title;
new_content_object["timestamp"] = time_now;
video_history_json.append(std::move(new_content_object));
- Path video_history_filepath = get_video_history_filepath(current_plugin);
+ Path video_history_filepath = get_video_history_filepath(plugin_name);
save_json_to_file_atomic(video_history_filepath, video_history_json);
}
};
bool has_video_started = true;
- auto video_event_callback = [this, &video_player, &load_video_error_check, previous_page, &has_video_started, &time_watched_timer, &video_loaded](const char *event_name) mutable {
+ auto video_event_callback = [this, &related_media_body, &video_url, &video_title, &video_player, &load_video_error_check, previous_page, &has_video_started, &time_watched_timer, &video_loaded](const char *event_name) mutable {
bool end_of_file = false;
if(strcmp(event_name, "pause") == 0) {
double time_remaining = 9999.0;
@@ -1518,8 +1552,8 @@ namespace QuickMedia {
return;
}
- content_url = std::move(new_video_url);
- content_title = std::move(new_video_title);
+ video_url = std::move(new_video_url);
+ video_title = std::move(new_video_title);
load_video_error_check();
}
};
@@ -1540,27 +1574,28 @@ namespace QuickMedia {
bool cursor_visible = true;
sf::Clock cursor_hide_timer;
- bool is_youtube = current_plugin->name == "youtube";
- bool is_pornhub = current_plugin->name == "pornhub";
+ bool is_youtube = strcmp(plugin_name, "youtube") == 0;
+ bool is_pornhub = strcmp(plugin_name, "pornhub") == 0;
bool supports_url_timestamp = is_youtube || is_pornhub;
- auto save_video_url_to_clipboard = [this, &video_player_window, &video_player, &supports_url_timestamp]() {
+ auto save_video_url_to_clipboard = [&video_url, &video_player_window, &video_player, &supports_url_timestamp]() {
if(!video_player_window)
return;
if(supports_url_timestamp) {
+ // TODO: Remove timestamp (&t= or ?t=) from video_url
double time_in_file;
if(video_player->get_time_in_file(&time_in_file) != VideoPlayer::Error::OK)
time_in_file = 0.0;
- sf::Clipboard::setString(content_url + "&t=" + std::to_string((int)time_in_file));
+ sf::Clipboard::setString(video_url + "&t=" + std::to_string((int)time_in_file));
} else {
- sf::Clipboard::setString(content_url);
+ sf::Clipboard::setString(video_url);
}
};
- while (current_page == Page::VIDEO_CONTENT) {
+ while (current_page == PageType::VIDEO_CONTENT) {
while (window.pollEvent(event)) {
- base_event_handler(event, previous_page, true, false, false);
+ base_event_handler(event, previous_page, related_media_body.get(), nullptr, true, false);
if(event.type == sf::Event::Resized && related_media_window) {
related_media_window_size.x = window_size.x * RELATED_MEDIA_WINDOW_WIDTH;
related_media_window_size.y = window_size.y;
@@ -1605,8 +1640,8 @@ namespace QuickMedia {
related_media_window->setVisible(false);
has_video_started = false;
- content_url = selected_item->url;
- content_title = selected_item->get_title();
+ video_url = selected_item->url;
+ video_title = selected_item->get_title();
load_video_error_check();
} else if(event.key.code == sf::Keyboard::C && event.key.control) {
save_video_url_to_clipboard();
@@ -1624,7 +1659,21 @@ namespace QuickMedia {
current_page = previous_page;
} else if(pressed_keysym == XK_f && pressing_ctrl) {
window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE);
- } else if(pressed_keysym == XK_r && pressing_ctrl && related_media_window) {
+ } else if(pressed_keysym == XK_r && pressing_ctrl && strcmp(plugin_name, "4chan") != 0) {
+ if(!related_media_window) {
+ related_media_window_size.x = window_size.x * RELATED_MEDIA_WINDOW_WIDTH;
+ related_media_window_size.y = window_size.y;
+ related_media_window = std::make_unique<sf::RenderWindow>(sf::VideoMode(related_media_window_size.x, related_media_window_size.y), "", 0, sf::ContextSettings(0, 0, 0, 3, 3));
+ related_media_window->setFramerateLimit(0);
+ if(!enable_vsync(disp, related_media_window->getSystemHandle())) {
+ fprintf(stderr, "Failed to enable vsync, fallback to frame limiting\n");
+ related_media_window->setFramerateLimit(monitor_hz);
+ }
+ related_media_window->setVisible(false);
+ XReparentWindow(disp, related_media_window->getSystemHandle(), video_player_window, window_size.x - related_media_window_size.x, 0);
+ XSync(disp, False);
+ }
+
related_media_window_visible = true;
related_media_window->setVisible(related_media_window_visible);
if(!cursor_visible)
@@ -1663,7 +1712,7 @@ namespace QuickMedia {
/* Only save recommendations for the video if we have been watching it for 15 seconds */
if(is_youtube && video_loaded && !added_recommendations && time_watched_timer.getElapsedTime().asSeconds() >= 15) {
added_recommendations = true;
- save_recommendations_from_related_videos();
+ save_recommendations_from_related_videos(video_url, video_title, related_media_body.get());
}
if(video_player_window) {
@@ -1701,37 +1750,14 @@ namespace QuickMedia {
window_size.y = window_size_u.y;
}
- enum class TrackMediaType {
- RSS,
- HTML
- };
-
- const char* track_media_type_string(TrackMediaType media_type) {
- switch(media_type) {
- case TrackMediaType::RSS:
- return "rss";
- case TrackMediaType::HTML:
- return "html";
- }
- assert(false);
- return "";
- }
-
- static int track_media(TrackMediaType media_type, const std::string &manga_title, const std::string &chapter_title, const std::string &url) {
- const char *args[] = { "automedia", "add", track_media_type_string(media_type), url.data(), "--start-after", chapter_title.data(), "--name", manga_title.data(), nullptr };
- return exec_program(args, nullptr, nullptr);
- }
-
void Program::select_episode(BodyItem *item, bool start_from_beginning) {
- images_url = item->url;
- chapter_title = item->get_title();
image_index = 0;
switch(image_view_mode) {
case ImageViewMode::SINGLE:
- current_page = Page::IMAGES;
+ current_page = PageType::IMAGES;
break;
case ImageViewMode::SCROLL:
- current_page = Page::IMAGES_CONTINUOUS;
+ current_page = PageType::IMAGES_CONTINUOUS;
break;
}
@@ -1740,7 +1766,7 @@ namespace QuickMedia {
const Json::Value &json_chapters = content_storage_json["chapters"];
if(json_chapters.isObject()) {
- const Json::Value &json_chapter = json_chapters[chapter_title];
+ const Json::Value &json_chapter = json_chapters[item->get_title()];
if(json_chapter.isObject()) {
const Json::Value &current = json_chapter["current"];
if(current.isNumeric())
@@ -1749,199 +1775,14 @@ namespace QuickMedia {
}
}
- Page Program::pop_page_stack() {
+ // TODO: Remove
+ PageType Program::pop_page_stack() {
if(!page_stack.empty()) {
- Page previous_page = page_stack.top();
+ PageType previous_page = page_stack.top();
page_stack.pop();
return previous_page;
}
- return Page::EXIT;
- }
-
- enum class EpisodeListTabType {
- CHAPTERS,
- CREATOR
- };
-
- struct EpisodeListTab {
- EpisodeListTabType type;
- Body *body;
- const Creator *creator;
- std::future<BodyItems> creator_page_download_future;
- sf::Text text;
- };
-
- void Program::episode_list_page() {
- assert(current_plugin->is_manga());
- Manga *manga = static_cast<Manga*>(current_plugin);
-
- Json::Value *json_chapters = &content_storage_json["chapters"];
- std::vector<EpisodeListTab> tabs;
- int selected_tab = 0;
-
- search_bar->onTextUpdateCallback = [&tabs, &selected_tab](const std::string &text) {
- tabs[selected_tab].body->filter_search_fuzzy(text);
- tabs[selected_tab].body->select_first_item();
- };
-
- search_bar->onTextSubmitCallback = [this, &tabs, &selected_tab, &json_chapters](const std::string&) -> bool {
- if(tabs[selected_tab].type == EpisodeListTabType::CHAPTERS) {
- BodyItem *selected_item = body->get_selected();
- if(!selected_item)
- return false;
-
- select_episode(selected_item, false);
- return true;
- } else {
- if(on_search_suggestion_submit_text(tabs[selected_tab].body, body)) {
- selected_tab = 0;
- json_chapters = &content_storage_json["chapters"];
- return true;
- } else {
- return false;
- }
- }
- };
-
- auto download_creator_page = [manga](std::string url) {
- BodyItems body_items;
- if(manga->get_creators_manga_list(url, body_items) != PluginResult::OK)
- show_notification("Manga", "Failed to download authors page", Urgency::CRITICAL);
- return body_items;
- };
-
- EpisodeListTab chapters_tab;
- chapters_tab.type = EpisodeListTabType::CHAPTERS;
- chapters_tab.body = body;
- chapters_tab.creator = nullptr;
- chapters_tab.text = sf::Text("Chapters", *font, tab_text_size);
- tabs.push_back(std::move(chapters_tab));
-
- const std::vector<Creator>& creators = manga->get_creators();
- for(const Creator &creator : creators) {
- EpisodeListTab tab;
- tab.type = EpisodeListTabType::CREATOR;
- tab.body = new Body(this, font.get(), bold_font.get(), cjk_font.get());
- tab.body->draw_thumbnails = true;
- tab.creator = &creator;
- tab.creator_page_download_future = std::async(std::launch::async, download_creator_page, creator.url);
- tab.text = sf::Text(creator.name, *font, tab_text_size);
- tabs.push_back(std::move(tab));
- }
-
- const float tab_spacer_height = 0.0f;
- sf::Vector2f body_pos;
- sf::Vector2f body_size;
- bool redraw = true;
- sf::Event event;
-
- sf::RectangleShape tab_shade;
- tab_shade.setFillColor(sf::Color(33, 38, 44));
-
- sf::RoundedRectangleShape tab_background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10);
- tab_background.setFillColor(tab_selected_color);
-
- while (current_page == Page::EPISODE_LIST) {
- while (window.pollEvent(event)) {
- base_event_handler(event, Page::SEARCH_SUGGESTION, false, true);
- if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus)
- redraw = true;
- else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::T && event.key.control && tabs[selected_tab].type == EpisodeListTabType::CHAPTERS) {
- BodyItem *selected_item = body->get_selected();
- if(selected_item) {
- if(track_media(TrackMediaType::HTML, content_title, selected_item->get_title(), content_url) == 0) {
- show_notification("Media tracker", "You are now tracking \"" + content_title + "\" after \"" + selected_item->get_title() + "\"", Urgency::LOW);
- } else {
- show_notification("Media tracker", "Failed to track media \"" + content_title + "\", chapter: \"" + selected_item->get_title() + "\"", Urgency::CRITICAL);
- }
- }
- } else if(event.key.code == sf::Keyboard::Up) {
- tabs[selected_tab].body->select_previous_item();
- } else if(event.key.code == sf::Keyboard::Down) {
- tabs[selected_tab].body->select_next_item();
- } else if(event.key.code == sf::Keyboard::PageUp) {
- tabs[selected_tab].body->select_previous_page();
- } else if(event.key.code == sf::Keyboard::PageDown) {
- tabs[selected_tab].body->select_next_page();
- } else if(event.key.code == sf::Keyboard::Home) {
- tabs[selected_tab].body->select_first_item();
- } else if(event.key.code == sf::Keyboard::End) {
- tabs[selected_tab].body->select_last_item();
- } else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::SEARCH_SUGGESTION;
- body->clear_items();
- body->reset_selected();
- search_bar->clear();
- } else if(event.key.code == sf::Keyboard::Left) {
- tabs[selected_tab].body->filter_search_fuzzy("");
- tabs[selected_tab].body->select_first_item();
- tabs[selected_tab].body->clear_thumbnails();
- selected_tab = std::max(0, selected_tab - 1);
- search_bar->clear();
- } else if(event.key.code == sf::Keyboard::Right) {
- tabs[selected_tab].body->filter_search_fuzzy("");
- tabs[selected_tab].body->select_first_item();
- tabs[selected_tab].body->clear_thumbnails();
- selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1);
- search_bar->clear();
- }
- }
- }
-
- if(redraw) {
- redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size, true);
- }
-
- search_bar->update();
-
- window.clear(back_color);
- search_bar->draw(window, false);
-
- const float width_per_tab = window_size.x / tabs.size();
- tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height));
-
- float tab_vertical_offset = search_bar->getBottomWithoutShadow();
- if(tabs[selected_tab].type == EpisodeListTabType::CHAPTERS)
- tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters);
- else
- tabs[selected_tab].body->draw(window, body_pos, body_size);
- const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f);
-
- tab_shade.setPosition(0.0f, tab_spacer_height + std::floor(tab_vertical_offset));
- tab_shade.setSize(sf::Vector2f(window_size.x, tab_height + 10.0f));
- window.draw(tab_shade);
-
- int i = 0;
- for(EpisodeListTab &tab : tabs) {
- if(tab.type == EpisodeListTabType::CREATOR
- && tab.creator_page_download_future.valid()
- && tab.creator_page_download_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
- {
- tab.body->items = tab.creator_page_download_future.get();
- tab.body->filter_search_fuzzy(search_bar->get_text());
- tab.body->select_first_item();
- }
-
- if(i == selected_tab) {
- tab_background.setPosition(std::floor(i * width_per_tab + tab_margin_x), tab_spacer_height + std::floor(tab_vertical_offset));
- window.draw(tab_background);
- }
- const float center = (i * width_per_tab) + (width_per_tab * 0.5f);
- tab.text.setPosition(std::floor(center - tab.text.getLocalBounds().width * 0.5f), tab_y);
- window.draw(tab.text);
- ++i;
- }
-
- window.display();
- }
-
- for(EpisodeListTab &tab : tabs) {
- if(tab.type == EpisodeListTabType::CREATOR)
- delete tab.body;
- }
+ return PageType::EXIT;
}
// TODO: Optimize this somehow. One image alone uses more than 20mb ram! Total ram usage for viewing one image
@@ -1980,24 +1821,23 @@ namespace QuickMedia {
}
}
- // TODO: Cancel download when navigating to another non-manga page
- void Program::download_chapter_images_if_needed(Manga *image_plugin) {
- if(downloading_chapter_url == images_url)
+ void Program::download_chapter_images_if_needed(MangaImagesPage *images_page) {
+ if(downloading_chapter_url == images_page->get_url())
return;
- downloading_chapter_url = images_url;
+ downloading_chapter_url = images_page->get_url();
if(image_download_future.valid()) {
+ // TODO: Cancel download instead of waiting for the last page to finish
image_download_cancel = true;
image_download_future.get();
image_download_cancel = false;
}
- std::string chapter_url = images_url;
Path content_cache_dir_ = content_cache_dir;
- image_download_future = std::async(std::launch::async, [chapter_url, image_plugin, content_cache_dir_, this]() {
+ image_download_future = std::async(std::launch::async, [images_page, content_cache_dir_, this]() {
// TODO: Download images in parallel
int page = 1;
- image_plugin->for_each_page_in_chapter(chapter_url, [content_cache_dir_, &page, this](const std::string &url) {
+ images_page->for_each_page_in_chapter([content_cache_dir_, &page, images_page, this](const std::string &url) {
if(image_download_cancel)
return false;
@@ -2019,7 +1859,7 @@ namespace QuickMedia {
return true;
std::vector<CommandArg> extra_args;
- if(current_plugin->name == "manganelo") {
+ if(strcmp(images_page->get_service_name(), "manganelo") == 0) {
extra_args = {
CommandArg { "-H", "accept: image/webp,image/apng,image/*,*/*;q=0.8" },
CommandArg { "-H", "sec-fetch-site: cross-site" },
@@ -2029,9 +1869,10 @@ namespace QuickMedia {
};
}
+ // TODO: Download directly to file instead. TODO: Move to page
std::string image_content;
- if(download_to_string(url, image_content, extra_args, current_plugin->use_tor, true) != DownloadResult::OK || image_content.size() <= 255) {
- if(current_plugin->name == "manganelo") {
+ if(download_to_string(url, image_content, extra_args, is_tor_enabled(), true) != DownloadResult::OK || image_content.size() <= 255) {
+ if(strcmp(images_page->get_service_name(), "manganelo") == 0) {
bool try_backup_url = false;
std::string new_url = url;
if(string_replace_all(new_url, "s3.mkklcdnv3.com", "bu.mkklcdnbuv1.com") > 0) {
@@ -2042,7 +1883,7 @@ namespace QuickMedia {
if(try_backup_url) {
image_content.clear();
- if(download_to_string(new_url, image_content, extra_args, current_plugin->use_tor, true) != DownloadResult::OK || image_content.size() <= 255) {
+ if(download_to_string(new_url, image_content, extra_args, is_tor_enabled(), true) != DownloadResult::OK || image_content.size() <= 255) {
show_notification("Manganelo", "Failed to download image: " + new_url, Urgency::CRITICAL);
return false;
}
@@ -2109,40 +1950,36 @@ namespace QuickMedia {
});
}
- void Program::image_page() {
+ int Program::image_page(MangaImagesPage *images_page, Body *chapters_body) {
+ int page_navigation = 0;
image_download_cancel = false;
- search_bar->onTextUpdateCallback = nullptr;
- search_bar->onTextSubmitCallback = nullptr;
sf::Texture image_texture;
sf::Sprite image;
sf::Text error_message("", *font, 30);
error_message.setFillColor(sf::Color::White);
- assert(current_plugin->is_manga());
- Manga *image_plugin = static_cast<Manga*>(current_plugin);
- std::string image_data;
bool download_in_progress = false;
- content_cache_dir = get_cache_dir().join(image_plugin->name).join(manga_id_base64).join(base64_encode(chapter_title));
+ content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_encode(images_page->get_chapter_name()));
if(create_directory_recursive(content_cache_dir) != 0) {
show_notification("Storage", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL);
- current_page = Page::EPISODE_LIST;
- return;
+ current_page = pop_page_stack();
+ return 0;
}
int num_images = 0;
- if(image_plugin->get_number_of_images(images_url, num_images) != ImageResult::OK) {
+ if(images_page->get_number_of_images(num_images) != ImageResult::OK) {
show_notification("Plugin", "Failed to get number of images", Urgency::CRITICAL);
- current_page = Page::EPISODE_LIST;
- return;
+ current_page = pop_page_stack();
+ return 0;
}
image_index = std::min(image_index, num_images);
if(num_images != (int)image_upscale_status.size())
image_upscale_status.resize(num_images);
- download_chapter_images_if_needed(image_plugin);
+ download_chapter_images_if_needed(images_page);
if(image_index < num_images) {
sf::String error_msg;
@@ -2153,14 +1990,15 @@ namespace QuickMedia {
download_in_progress = true;
error_message.setString(error_msg);
} else if(image_index == num_images) {
- error_message.setString("End of " + chapter_title);
+ error_message.setString("End of " + images_page->get_chapter_name());
}
+ // TODO: Dont do this every time we change page?
Json::Value &json_chapters = content_storage_json["chapters"];
Json::Value json_chapter;
int latest_read = image_index + 1;
if(json_chapters.isObject()) {
- json_chapter = json_chapters[chapter_title];
+ json_chapter = json_chapters[images_page->get_chapter_name()];
if(json_chapter.isObject()) {
const Json::Value &current = json_chapter["current"];
if(current.isNumeric())
@@ -2174,7 +2012,7 @@ namespace QuickMedia {
}
json_chapter["current"] = std::min(latest_read, num_images);
json_chapter["total"] = num_images;
- json_chapters[chapter_title] = json_chapter;
+ json_chapters[images_page->get_chapter_name()] = json_chapter;
if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) {
show_notification("Manga progress", "Failed to save manga progress", Urgency::CRITICAL);
}
@@ -2182,9 +2020,9 @@ namespace QuickMedia {
bool error = !error_message.getString().isEmpty();
bool redraw = true;
- sf::Text chapter_text(content_title + " | " + chapter_title + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_images), *font, 14);
+ sf::Text chapter_text(images_page->manga_name + " | " + images_page->get_chapter_name() + " | Page " + std::to_string(image_index + 1) + "/" + std::to_string(num_images), *font, 14);
if(image_index == num_images)
- chapter_text.setString(content_title + " | " + chapter_title + " | End");
+ chapter_text.setString(images_page->manga_name + " | " + images_page->get_chapter_name() + " | End");
chapter_text.setFillColor(sf::Color::White);
sf::RectangleShape chapter_text_background;
chapter_text_background.setFillColor(sf::Color(0, 0, 0, 150));
@@ -2204,10 +2042,11 @@ namespace QuickMedia {
while(window.pollEvent(event)) {}
// TODO: Show to user if a certain page is missing (by checking page name (number) and checking if some are skipped)
- while (current_page == Page::IMAGES) {
+ while (current_page == PageType::IMAGES) {
while(window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
- current_page = Page::EXIT;
+ current_page = PageType::EXIT;
+ window.close();
} else if(event.type == sf::Event::Resized) {
window_size.x = event.size.width;
window_size.y = event.size.height;
@@ -2221,29 +2060,22 @@ namespace QuickMedia {
if(image_index > 0) {
--image_index;
goto end_of_images_page;
- } else if(image_index == 0 && body->get_selected_item() < (int)body->items.size() - 1) {
- // TODO: Make this work if the list is sorted differently than from newest to oldest.
- body->filter_search_fuzzy("");
- body->select_next_item();
- select_episode(body->items[body->get_selected_item()].get(), true);
- image_index = 99999; // Start at the page that shows we are at the end of the chapter
+ } else if(image_index == 0 && chapters_body->get_selected_item() < (int)chapters_body->items.size() - 1) {
+ page_navigation = -1;
goto end_of_images_page;
}
} else if(event.key.code == sf::Keyboard::Down) {
if(image_index < num_images) {
++image_index;
goto end_of_images_page;
- } else if(image_index == num_images && body->get_selected_item() > 0) {
- // TODO: Make this work if the list is sorted differently than from newest to oldest.
- body->filter_search_fuzzy("");
- body->select_previous_item();
- select_episode(body->items[body->get_selected_item()].get(), true);
+ } else if(image_index == num_images && chapters_body->get_selected_item() > 0) {
+ page_navigation = 1;
goto end_of_images_page;
}
} else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::EPISODE_LIST;
+ current_page = pop_page_stack();
} else if(event.key.code == sf::Keyboard::I) {
- current_page = Page::IMAGES_CONTINUOUS;
+ current_page = PageType::IMAGES_CONTINUOUS;
image_view_mode = ImageViewMode::SCROLL;
} else if(event.key.code == sf::Keyboard::F) {
fit_image_to_window = !fit_image_to_window;
@@ -2317,46 +2149,47 @@ namespace QuickMedia {
}
end_of_images_page:
- if(current_page != Page::IMAGES && current_page != Page::IMAGES_CONTINUOUS) {
+ if(current_page != PageType::IMAGES && current_page != PageType::IMAGES_CONTINUOUS) {
image_download_cancel = true;
+ if(image_download_future.valid()) {
+ // TODO: Cancel download instead of waiting for the last page to finish
+ image_download_future.get();
+ image_download_cancel = false;
+ }
std::unique_lock<std::mutex> lock(image_upscale_mutex);
images_to_upscale.clear();
image_upscale_status.clear();
}
+ return page_navigation;
}
- void Program::image_continuous_page() {
+ void Program::image_continuous_page(MangaImagesPage *images_page) {
image_download_cancel = false;
- search_bar->onTextUpdateCallback = nullptr;
- search_bar->onTextSubmitCallback = nullptr;
-
- assert(current_plugin->is_manga());
- Manga *image_plugin = static_cast<Manga*>(current_plugin);
- content_cache_dir = get_cache_dir().join(image_plugin->name).join(manga_id_base64).join(base64_encode(chapter_title));
+ content_cache_dir = get_cache_dir().join(images_page->get_service_name()).join(manga_id_base64).join(base64_encode(images_page->get_chapter_name()));
if(create_directory_recursive(content_cache_dir) != 0) {
show_notification("Storage", "Failed to create directory: " + content_cache_dir.data, Urgency::CRITICAL);
- current_page = Page::EPISODE_LIST;
+ current_page = pop_page_stack();
return;
}
int num_images = 0;
- if(image_plugin->get_number_of_images(images_url, num_images) != ImageResult::OK) {
+ if(images_page->get_number_of_images(num_images) != ImageResult::OK) {
show_notification("Plugin", "Failed to get number of images", Urgency::CRITICAL);
- current_page = Page::EPISODE_LIST;
+ current_page = pop_page_stack();
return;
}
if(num_images != (int)image_upscale_status.size())
image_upscale_status.resize(num_images);
- download_chapter_images_if_needed(image_plugin);
+ download_chapter_images_if_needed(images_page);
Json::Value &json_chapters = content_storage_json["chapters"];
Json::Value json_chapter;
int latest_read = 1 + image_index;
if(json_chapters.isObject()) {
- json_chapter = json_chapters[chapter_title];
+ json_chapter = json_chapters[images_page->get_chapter_name()];
if(json_chapter.isObject()) {
const Json::Value &current = json_chapter["current"];
if(current.isNumeric())
@@ -2369,27 +2202,27 @@ namespace QuickMedia {
json_chapter = Json::Value(Json::objectValue);
}
- ImageViewer image_viewer(image_plugin, images_url, content_title, chapter_title, image_index, content_cache_dir, font.get());
+ ImageViewer image_viewer(images_page, images_page->manga_name, images_page->get_chapter_name(), image_index, content_cache_dir, font.get());
json_chapter["current"] = std::min(latest_read, image_viewer.get_num_pages());
json_chapter["total"] = image_viewer.get_num_pages();
- json_chapters[chapter_title] = json_chapter;
+ json_chapters[images_page->get_chapter_name()] = json_chapter;
if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) {
show_notification("Manga progress", "Failed to save manga progress", Urgency::CRITICAL);
}
- while(current_page == Page::IMAGES_CONTINUOUS) {
+ while(current_page == PageType::IMAGES_CONTINUOUS) {
window.clear(back_color);
ImageViewerAction action = image_viewer.draw(window);
switch(action) {
case ImageViewerAction::NONE:
break;
case ImageViewerAction::RETURN:
- current_page = Page::EPISODE_LIST;
+ current_page = pop_page_stack();
break;
case ImageViewerAction::SWITCH_TO_SINGLE_IMAGE_MODE:
image_view_mode = ImageViewMode::SINGLE;
- current_page = Page::IMAGES;
+ current_page = PageType::IMAGES;
break;
}
window.display();
@@ -2399,414 +2232,31 @@ namespace QuickMedia {
if(focused_page > latest_read) {
latest_read = focused_page;
json_chapter["current"] = latest_read;
- json_chapters[chapter_title] = json_chapter;
+ json_chapters[images_page->get_chapter_name()] = json_chapter;
if(!save_json_to_file_atomic(content_storage_file, content_storage_json)) {
show_notification("Manga progress", "Failed to save manga progress", Urgency::CRITICAL);
}
}
}
- if(current_page != Page::IMAGES && current_page != Page::IMAGES_CONTINUOUS) {
+ if(current_page != PageType::IMAGES && current_page != PageType::IMAGES_CONTINUOUS) {
image_download_cancel = true;
+ if(image_download_future.valid()) {
+ // TODO: Cancel download instead of waiting for the last page to finish
+ image_download_future.get();
+ image_download_cancel = false;
+ }
std::unique_lock<std::mutex> lock(image_upscale_mutex);
images_to_upscale.clear();
image_upscale_status.clear();
}
}
- void Program::content_list_page() {
- std::string update_search_text;
- bool search_text_updated = false;
- bool search_running = false;
- std::future<BodyItems> search_future;
-
- if(!current_plugin->content_list_search_is_filter())
- search_bar->text_autosearch_delay = current_plugin->get_content_list_search_delay();
-
- body->clear_items();
- body->clear_thumbnails();
- if(current_plugin->get_content_list(content_list_url, body->items) != PluginResult::OK) {
- show_notification("Content list", "Failed to get content list for url: " + content_list_url, Urgency::CRITICAL);
- current_page = Page::SEARCH_SUGGESTION;
- return;
- }
-
- search_bar->onTextUpdateCallback = [this, &update_search_text, &search_text_updated](const std::string &text) {
- if(current_plugin->content_list_search_is_filter()) {
- body->filter_search_fuzzy(text);
- body->select_first_item();
- } else {
- update_search_text = text;
- search_text_updated = true;
- }
- };
-
- search_bar->onTextSubmitCallback = [this](const std::string&) -> bool {
- BodyItem *selected_item = body->get_selected();
- if(!selected_item)
- return false;
-
- content_episode = selected_item->get_title();
- content_url = selected_item->url;
- current_page = Page::CONTENT_DETAILS;
- body->clear_items();
- return true;
- };
-
- int fetched_page = 0;
- bool fetching_next_page_running = false;
- double gradient_inc = 0;
- const float gradient_height = 5.0f;
- sf::Vertex gradient_points[4];
- std::future<BodyItems> next_page_future;
-
- sf::Vector2f body_pos;
- sf::Vector2f body_size;
- bool redraw = true;
- sf::Event event;
- sf::Clock frame_timer;
-
- while (current_page == Page::CONTENT_LIST) {
- sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
-
- while (window.pollEvent(event)) {
- base_event_handler(event, Page::SEARCH_SUGGESTION, false);
- if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) {
- redraw = true;
- } else if(event.type == sf::Event::KeyPressed) {
- if(event.key.code == sf::Keyboard::Down || event.key.code == sf::Keyboard::PageDown || event.key.code == sf::Keyboard::End) {
- bool hit_bottom = false;
- switch(event.key.code) {
- case sf::Keyboard::Down:
- hit_bottom = !body->select_next_item();
- break;
- case sf::Keyboard::PageDown:
- hit_bottom = !body->select_next_page();
- break;
- case sf::Keyboard::End:
- body->select_last_item();
- hit_bottom = true;
- break;
- default:
- hit_bottom = false;
- break;
- }
- if(hit_bottom && !search_running && !fetching_next_page_running) {
- gradient_inc = 0;
- fetching_next_page_running = true;
- int next_page = fetched_page + 1;
- std::string content_list_url_copy = content_list_url;
- std::string update_search_text_copy = update_search_text;
- next_page_future = std::async(std::launch::async, [this, content_list_url_copy, update_search_text_copy, next_page]() {
- BodyItems result_items;
- if(current_plugin->content_list_search_page(content_list_url_copy, update_search_text_copy, next_page, result_items) != SearchResult::OK)
- fprintf(stderr, "Failed to get next content list page (page %d)\n", next_page);
- return result_items;
- });
- }
- } else if(event.key.code == sf::Keyboard::Up) {
- body->select_previous_item();
- } else if(event.key.code == sf::Keyboard::PageUp) {
- body->select_previous_page();
- } else if(event.key.code == sf::Keyboard::Home) {
- body->select_first_item();
- } else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::SEARCH_SUGGESTION;
- body->clear_items();
- body->reset_selected();
- search_bar->clear();
- }
- }
- }
-
- if(redraw) {
- redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size);
-
- gradient_points[0].position.x = 0.0f;
- gradient_points[0].position.y = window_size.y - gradient_height;
-
- gradient_points[1].position.x = window_size.x;
- gradient_points[1].position.y = window_size.y - gradient_height;
-
- gradient_points[2].position.x = window_size.x;
- gradient_points[2].position.y = window_size.y;
-
- gradient_points[3].position.x = 0.0f;
- gradient_points[3].position.y = window_size.y;
- }
-
- search_bar->update();
-
- if(search_text_updated && !fetching_next_page_running && !search_running) {
- std::string search_term = update_search_text;
- search_future = std::async(std::launch::async, [this, search_term]() {
- BodyItems result;
- if(current_plugin->content_list_search(content_list_url, search_term, result) != SearchResult::OK) {
- // TODO: Show this?
- //show_notification("Search", "Search failed!", Urgency::CRITICAL);
- }
- return result;
- });
- search_text_updated = false;
- search_running = true;
- }
-
- if(search_running && search_future.valid() && search_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- if(!search_text_updated) {
- body->items = search_future.get();
- body->select_first_item();
- } else {
- search_future.get();
- }
- search_running = false;
- fetched_page = 0;
- }
-
- if(fetching_next_page_running && next_page_future.valid() && next_page_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
- BodyItems new_body_items = next_page_future.get();
- fprintf(stderr, "Finished fetching page %d, num new messages: %zu\n", fetched_page + 1, new_body_items.size());
- size_t num_new_messages = new_body_items.size();
- if(num_new_messages > 0) {
- body->append_items(std::move(new_body_items));
- fetched_page++;
- }
- fetching_next_page_running = false;
- }
-
- window.clear(back_color);
- search_bar->draw(window);
- body->draw(window, body_pos, body_size);
- if(fetching_next_page_running) {
- double progress = 0.5 + std::sin(std::fmod(gradient_inc, 360.0) * 0.017453292519943295 - 1.5707963267948966*0.5) * 0.5;
- gradient_inc += (frame_time_ms * 0.5);
- sf::Color bottom_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress);
-
- gradient_points[0].color = back_color;
- gradient_points[1].color = back_color;
- gradient_points[2].color = bottom_color;
- gradient_points[3].color = bottom_color;
- window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl
- }
- window.display();
- }
-
- search_bar->text_autosearch_delay = current_plugin->get_search_delay();
- }
-
- void Program::content_details_page() {
- if(current_plugin->get_content_details(content_list_url, content_url, body->items) != PluginResult::OK) {
- show_notification("Content details", "Failed to get content details for url: " + content_url, Urgency::CRITICAL);
- // TODO: This will return to an empty content list.
- // Each page should have its own @Body so we can return to the last page and still have the data loaded
- // however the cached images should be cleared.
- current_page = Page::CONTENT_LIST;
- return;
- }
-
- search_bar->onTextUpdateCallback = nullptr;
-
- search_bar->onTextSubmitCallback = [this](const std::string&) -> bool {
- if(current_plugin->name == "nyaa.si") {
- BodyItem *selected_item = body->get_selected();
- if(selected_item && strncmp(selected_item->url.c_str(), "magnet:?", 8) == 0) {
- if(!is_program_executable_by_name("xdg-open")) {
- show_notification("Nyaa.si", "xdg-utils which provides xdg-open needs to be installed to download torrents", Urgency::CRITICAL);
- return false;
- }
- const char *args[] = { "xdg-open", selected_item->url.c_str(), nullptr };
- exec_program_async(args, nullptr);
- }
- }
- return false;
- };
-
- sf::Vector2f body_pos;
- sf::Vector2f body_size;
- bool redraw = true;
- sf::Event event;
-
- while (current_page == Page::CONTENT_DETAILS) {
- while (window.pollEvent(event)) {
- base_event_handler(event, Page::CONTENT_LIST);
- if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus)
- redraw = true;
- }
-
- if(redraw) {
- redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size);
- }
-
- search_bar->update();
-
- window.clear(back_color);
- search_bar->draw(window);
- body->draw(window, body_pos, body_size);
- window.display();
- }
- }
-
- void Program::file_manager_page() {
- selected_files.clear();
- search_bar->clear();
- int prev_autosearch_delay = search_bar->text_autosearch_delay;
- search_bar->text_autosearch_delay = file_manager->get_search_delay();
- Page previous_page = pop_page_stack();
-
- sf::Text current_dir_text(file_manager->get_current_dir().string(), *bold_font, 18);
-
- // TODO: Make asynchronous.
- // TODO: Automatically go to the parent if this fails (recursively).
- body->select_first_item();
- body->items.clear();
- if(file_manager->get_files_in_directory(body->items) != PluginResult::OK) {
- show_notification("QuickMedia", "File manager failed to get files in directory: " + file_manager->get_current_dir().string(), Urgency::CRITICAL);
- }
-
- search_bar->onTextUpdateCallback = [this](const std::string &text) {
- body->filter_search_fuzzy(text);
- body->reset_selected();
- };
-
- search_bar->onTextSubmitCallback = [this, previous_page, &current_dir_text](const std::string&) -> bool {
- BodyItem *selected_item = body->get_selected();
- if(!selected_item)
- return false;
-
- if(file_manager->set_child_directory(selected_item->get_title())) {
- std::string current_dir_str = file_manager->get_current_dir().string();
- current_dir_text.setString(current_dir_str);
- // TODO: Make asynchronous.
- // TODO: Automatically go to the parent if this fails (recursively).
- body->items.clear();
- if(file_manager->get_files_in_directory(body->items) != PluginResult::OK) {
- show_notification("QuickMedia", "File manager failed to get files in directory: " + current_dir_str, Urgency::CRITICAL);
- }
- body->select_first_item();
- return true;
- } else {
- std::filesystem::path full_path = file_manager->get_current_dir() / selected_item->get_title();
- selected_files.push_back(full_path.string());
- printf("%s\n", selected_files.back().c_str());
- current_page = previous_page;
- return false;
- }
- };
-
- sf::Vector2f body_pos;
- sf::Vector2f body_size;
- bool redraw = true;
- sf::Event event;
-
- while (current_page == Page::FILE_MANAGER) {
- while (window.pollEvent(event)) {
- base_event_handler(event, previous_page);
- if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus)
- redraw = true;
- }
-
- if(redraw) {
- redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size);
- const float dir_text_height = std::floor(current_dir_text.getLocalBounds().height + 12.0f);
- body_pos.y += dir_text_height;
- body_size.y -= dir_text_height;
- current_dir_text.setPosition(body_pos.x, body_pos.y - dir_text_height);
- }
-
- search_bar->update();
- window.clear(back_color);
- search_bar->draw(window);
- body->draw(window, body_pos, body_size);
- window.draw(current_dir_text);
- window.display();
- }
-
- search_bar->text_autosearch_delay = prev_autosearch_delay;
- // We want exit code 1 if the file manager was launched and no files were selected, to know when the user didn't select any file(s)
- if(selected_files.empty() && current_page == Page::EXIT)
- exit_code = 1;
- }
-
- void Program::image_board_thread_list_page() {
- assert(current_plugin->is_image_board());
- ImageBoard *image_board = static_cast<ImageBoard*>(current_plugin);
- if(image_board->get_threads(image_board_thread_list_url, body->items) != PluginResult::OK) {
- show_notification("Content list", "Failed to get threads for url: " + image_board_thread_list_url, Urgency::CRITICAL);
- current_page = Page::SEARCH_SUGGESTION;
- return;
- }
-
- search_bar->onTextUpdateCallback = [this](const std::string &text) {
- body->filter_search_fuzzy(text);
- body->select_first_item();
- };
-
- search_bar->onTextSubmitCallback = [this](const std::string&) -> bool {
- BodyItem *selected_item = body->get_selected();
- if(!selected_item)
- return false;
-
- content_episode = selected_item->get_title();
- content_url = selected_item->url;
- current_page = Page::IMAGE_BOARD_THREAD;
- body->clear_items();
- return true;
- };
-
- sf::Vector2f body_pos;
- sf::Vector2f body_size;
- bool redraw = true;
- sf::Event event;
-
- while (current_page == Page::IMAGE_BOARD_THREAD_LIST) {
- while (window.pollEvent(event)) {
- base_event_handler(event, Page::SEARCH_SUGGESTION);
- if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus)
- redraw = true;
- }
-
- if(redraw) {
- redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size);
- }
-
- search_bar->update();
-
- window.clear(back_color);
- search_bar->draw(window);
- body->draw(window, body_pos, body_size);
- window.display();
- }
- }
-
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() {
- assert(current_plugin->is_image_board());
- // TODO: Support image board other than 4chan. To make this work, the captcha code needs to be changed
- // to work with other captcha than google captcha
- assert(current_plugin->name == "4chan");
- ImageBoard *image_board = static_cast<ImageBoard*>(current_plugin);
- if(image_board->get_thread_comments(image_board_thread_list_url, content_url, body->items) != PluginResult::OK) {
- show_notification("Content details", "Failed to get content details for url: " + content_url, Urgency::CRITICAL);
- // TODO: This will return to an empty content list.
- // Each page should have its own @Body so we can return to the last page and still have the data loaded
- // however the cached images should be cleared.
- current_page = Page::IMAGE_BOARD_THREAD_LIST;
- return;
- }
-
- const std::string &board = image_board_thread_list_url;
- const std::string &thread = content_url;
-
+ 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 {
VIEWING_COMMENTS,
@@ -2857,7 +2307,7 @@ namespace QuickMedia {
// TODO: Make this work with other sites than 4chan
auto request_google_captcha_image = [this, &captcha_texture, &captcha_image_mutex, &navigation_stage, &captcha_sprite, &challenge_description_text](GoogleCaptchaChallengeInfo &challenge_info) {
std::string payload_image_data;
- DownloadResult download_image_result = download_to_string(challenge_info.payload_url, payload_image_data, {}, current_plugin->use_tor);
+ DownloadResult download_image_result = download_to_string(challenge_info.payload_url, payload_image_data, {}, is_tor_enabled());
if(download_image_result == DownloadResult::OK) {
std::lock_guard<std::mutex> lock(captcha_image_mutex);
if(captcha_texture.loadFromMemory(payload_image_data.data(), payload_image_data.size())) {
@@ -2893,40 +2343,40 @@ namespace QuickMedia {
show_notification("Google captcha", "Failed to get captcha challenge", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
}
- }, current_plugin->use_tor);
+ }, is_tor_enabled());
};
Entry comment_input("Press ctrl+m to begin writing a comment...", font.get(), cjk_font.get());
comment_input.draw_background = false;
comment_input.set_editable(false);
- auto post_comment = [this, &comment_input, &navigation_stage, &image_board, &board, &thread, &captcha_post_id, &comment_to_post, &request_new_google_captcha_challenge]() {
+ auto post_comment = [this, &comment_input, &navigation_stage, &thread_page, &captcha_post_id, &comment_to_post, &request_new_google_captcha_challenge]() {
comment_input.set_editable(false);
navigation_stage = NavigationStage::POSTING_COMMENT;
- PostResult post_result = image_board->post_comment(board, thread, captcha_post_id, comment_to_post);
+ PostResult post_result = thread_page->post_comment(captcha_post_id, comment_to_post);
if(post_result == PostResult::OK) {
- show_notification(current_plugin->name, "Comment posted!");
+ show_notification("QuickMedia", "Comment posted!");
navigation_stage = NavigationStage::VIEWING_COMMENTS;
// TODO: Append posted comment to the thread so the user can see their posted comment.
// TODO: Asynchronously update the thread periodically to show new comments.
} else if(post_result == PostResult::TRY_AGAIN) {
- show_notification(current_plugin->name, "Error while posting, did the captcha expire? Please try again");
+ show_notification("QuickMedia", "Error while posting, did the captcha expire? Please try again");
// TODO: Check if the response contains a new captcha instead of requesting a new one manually
request_new_google_captcha_challenge();
} else if(post_result == PostResult::BANNED) {
- show_notification(current_plugin->name, "Failed to post comment because you are banned", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to post comment because you are banned", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
} else if(post_result == PostResult::ERR) {
- show_notification(current_plugin->name, "Failed to post comment. Is " + current_plugin->name + " down or is your internet down?", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to post comment. Is " + std::string(plugin_name) + " down or is your internet down?", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
} else {
assert(false && "Unhandled post result");
- show_notification(current_plugin->name, "Failed to post comment. Unknown error", Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to post comment. Unknown error", Urgency::CRITICAL);
navigation_stage = NavigationStage::VIEWING_COMMENTS;
}
};
- comment_input.on_submit_callback = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &image_board](const std::string &text) -> bool {
+ comment_input.on_submit_callback = [&post_comment_future, &navigation_stage, &request_new_google_captcha_challenge, &comment_to_post, &captcha_post_id, &captcha_solved_time, &post_comment, &thread_page](const std::string &text) -> bool {
if(text.empty())
return false;
@@ -2937,9 +2387,9 @@ namespace QuickMedia {
post_comment();
return true;
});
- } else if(image_board->get_pass_id().empty()) {
+ } else if(thread_page->get_pass_id().empty()) {
request_new_google_captcha_challenge();
- } else if(!image_board->get_pass_id().empty()) {
+ } else if(!thread_page->get_pass_id().empty()) {
post_comment_future = std::async(std::launch::async, [&post_comment]() -> bool {
post_comment();
return true;
@@ -2967,7 +2417,7 @@ namespace QuickMedia {
std::stack<int> comment_navigation_stack;
std::stack<int> comment_page_scroll_stack;
- while (current_page == Page::IMAGE_BOARD_THREAD) {
+ while (current_page == PageType::IMAGE_BOARD_THREAD) {
while (window.pollEvent(event)) {
if(navigation_stage == NavigationStage::REPLYING) {
comment_input.process_event(event);
@@ -2981,8 +2431,9 @@ namespace QuickMedia {
}
}
- if (event.type == sf::Event::Closed) {
- current_page = Page::EXIT;
+ if(event.type == sf::Event::Closed) {
+ current_page = PageType::EXIT;
+ window.close();
} else if(event.type == sf::Event::Resized) {
window_size.x = event.size.width;
window_size.y = event.size.height;
@@ -2994,46 +2445,42 @@ namespace QuickMedia {
redraw = true;
else if(event.type == sf::Event::KeyPressed && navigation_stage == NavigationStage::VIEWING_COMMENTS) {
if(event.key.code == sf::Keyboard::Up) {
- body->select_previous_item();
+ thread_body->select_previous_item();
} else if(event.key.code == sf::Keyboard::Down) {
- body->select_next_item();
+ thread_body->select_next_item();
} else if(event.key.code == sf::Keyboard::PageUp) {
- body->select_previous_page();
+ thread_body->select_previous_page();
} else if(event.key.code == sf::Keyboard::PageDown) {
- body->select_next_page();
+ thread_body->select_next_page();
} else if(event.key.code == sf::Keyboard::Home) {
- body->select_first_item();
+ thread_body->select_first_item();
} else if(event.key.code == sf::Keyboard::End) {
- body->select_last_item();
+ thread_body->select_last_item();
} else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::IMAGE_BOARD_THREAD_LIST;
- body->clear_items();
- body->reset_selected();
+ current_page = pop_page_stack();
} else if(event.key.code == sf::Keyboard::P) {
- BodyItem *selected_item = body->get_selected();
+ BodyItem *selected_item = thread_body->get_selected();
if(selected_item && !selected_item->attached_content_url.empty()) {
if(is_url_video(selected_item->attached_content_url)) {
- page_stack.push(Page::IMAGE_BOARD_THREAD);
- current_page = Page::VIDEO_CONTENT;
- std::string prev_content_url = content_url;
- content_url = selected_item->attached_content_url;
+ page_stack.push(PageType::IMAGE_BOARD_THREAD);
+ current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
- video_content_page();
- content_url = std::move(prev_content_url);
+ // TODO: Use real title
+ video_content_page(thread_page, selected_item->attached_content_url, "No title.webm");
redraw = true;
} else {
if(downloading_image && load_image_future.valid())
load_image_future.get();
downloading_image = true;
navigation_stage = NavigationStage::VIEWING_ATTACHED_IMAGE;
- load_image_future = std::async(std::launch::async, [this, &image_board]() {
+ load_image_future = std::async(std::launch::async, [this, &thread_body]() {
std::string image_data;
- BodyItem *selected_item = body->get_selected();
+ BodyItem *selected_item = thread_body->get_selected();
if(!selected_item || selected_item->attached_content_url.empty()) {
return image_data;
}
- if(download_to_string_cache(selected_item->attached_content_url, image_data, {}, image_board->use_tor) != DownloadResult::OK) {
- show_notification(image_board->name, "Failed to download image: " + selected_item->attached_content_url, Urgency::CRITICAL);
+ if(download_to_string_cache(selected_item->attached_content_url, image_data, {}, is_tor_enabled()) != DownloadResult::OK) {
+ show_notification("QuickMedia", "Failed to download image: " + selected_item->attached_content_url, Urgency::CRITICAL);
image_data.clear();
}
return image_data;
@@ -3042,43 +2489,43 @@ namespace QuickMedia {
}
}
- BodyItem *selected_item = body->get_selected();
- if(event.key.code == sf::Keyboard::Enter && selected_item && (comment_navigation_stack.empty() || body->get_selected_item() != comment_navigation_stack.top()) && !selected_item->replies.empty()) {
- for(auto &body_item : body->items) {
+ BodyItem *selected_item = thread_body->get_selected();
+ if(event.key.code == sf::Keyboard::Enter && selected_item && (comment_navigation_stack.empty() || thread_body->get_selected_item() != comment_navigation_stack.top()) && !selected_item->replies.empty()) {
+ for(auto &body_item : thread_body->items) {
body_item->visible = false;
}
selected_item->visible = true;
for(size_t reply_index : selected_item->replies) {
- body->items[reply_index]->visible = true;
+ thread_body->items[reply_index]->visible = true;
}
- comment_navigation_stack.push(body->get_selected_item());
- comment_page_scroll_stack.push(body->get_page_scroll());
- body->clamp_selection();
- body->set_page_scroll(0.0f);
+ comment_navigation_stack.push(thread_body->get_selected_item());
+ comment_page_scroll_stack.push(thread_body->get_page_scroll());
+ thread_body->clamp_selection();
+ thread_body->set_page_scroll(0.0f);
} else if(event.key.code == sf::Keyboard::BackSpace && !comment_navigation_stack.empty()) {
size_t previous_selected = comment_navigation_stack.top();
float previous_page_scroll = comment_page_scroll_stack.top();
comment_navigation_stack.pop();
comment_page_scroll_stack.pop();
if(comment_navigation_stack.empty()) {
- for(auto &body_item : body->items) {
+ for(auto &body_item : thread_body->items) {
body_item->visible = true;
}
- body->set_selected_item(previous_selected);
- body->clamp_selection();
+ thread_body->set_selected_item(previous_selected);
+ thread_body->clamp_selection();
} else {
- for(auto &body_item : body->items) {
+ for(auto &body_item : thread_body->items) {
body_item->visible = false;
}
- body->set_selected_item(previous_selected);
- selected_item = body->items[comment_navigation_stack.top()].get();
+ thread_body->set_selected_item(previous_selected);
+ selected_item = thread_body->items[comment_navigation_stack.top()].get();
selected_item->visible = true;
for(size_t reply_index : selected_item->replies) {
- body->items[reply_index]->visible = true;
+ thread_body->items[reply_index]->visible = true;
}
- body->clamp_selection();
+ thread_body->clamp_selection();
}
- body->set_page_scroll(previous_page_scroll);
+ thread_body->set_page_scroll(previous_page_scroll);
} else if(event.key.code == sf::Keyboard::M && event.key.control && selected_item) {
navigation_stage = NavigationStage::REPLYING;
comment_input.set_editable(true);
@@ -3126,7 +2573,7 @@ namespace QuickMedia {
}
request_google_captcha_image(challenge_info);
}
- }, current_plugin->use_tor);
+ }, is_tor_enabled());
}
}
@@ -3226,11 +2673,11 @@ namespace QuickMedia {
//attached_image_texture->generateMipmap();
attached_image_sprite.setTexture(*attached_image_texture, true);
} else {
- BodyItem *selected_item = body->get_selected();
+ BodyItem *selected_item = thread_body->get_selected();
std::string selected_item_attached_url;
if(selected_item)
selected_item_attached_url = selected_item->attached_content_url;
- show_notification(image_board->name, "Failed to load image downloaded from url: " + selected_item->attached_content_url, Urgency::CRITICAL);
+ show_notification("QuickMedia", "Failed to load image downloaded from url: " + selected_item->attached_content_url, Urgency::CRITICAL);
}
}
@@ -3260,12 +2707,12 @@ namespace QuickMedia {
window.draw(comment_input_shade);
window.draw(logo_sprite);
comment_input.draw(window);
- body->draw(window, body_pos, body_size);
+ thread_body->draw(window, body_pos, body_size);
} else if(navigation_stage == NavigationStage::VIEWING_COMMENTS) {
window.draw(comment_input_shade);
window.draw(logo_sprite);
comment_input.draw(window);
- body->draw(window, body_pos, body_size);
+ thread_body->draw(window, body_pos, body_size);
}
window.display();
}
@@ -3279,14 +2726,10 @@ namespace QuickMedia {
post_comment_future.get();
if(load_image_future.valid())
load_image_future.get();
-
- // Clear post that is still being written.
- // TODO: This post should be saved for the thread. Each thread should have its own text edit widget,
- // so you dont have to retype a post that was in the middle of being posted when returning.
}
void Program::chat_login_page() {
- assert(current_plugin->name == "matrix");
+ assert(strcmp(plugin_name, "matrix") == 0);
SearchBar login_input(*font, nullptr, "Username");
SearchBar password_input(*font, nullptr, "Password", true);
@@ -3298,23 +2741,22 @@ namespace QuickMedia {
SearchBar *inputs[num_inputs] = { &login_input, &password_input, &homeserver_input };
int focused_input = 0;
- auto text_submit_callback = [this, inputs, &status_text](const sf::String&) -> bool {
- Matrix *matrix = static_cast<Matrix*>(current_plugin);
+ auto text_submit_callback = [this, inputs, &status_text](const sf::String&) {
for(int i = 0; i < num_inputs; ++i) {
if(inputs[i]->get_text().empty()) {
status_text.setString("All fields need to be filled in");
- return false;
+ return;
}
}
std::string err_msg;
// TODO: Make asynchronous
if(matrix->login(inputs[0]->get_text(), inputs[1]->get_text(), inputs[2]->get_text(), err_msg) == PluginResult::OK) {
- current_page = Page::CHAT;
+ current_page = PageType::CHAT;
} else {
status_text.setString("Failed to login, error: " + err_msg);
}
- return false;
+ return;
};
for(int i = 0; i < num_inputs; ++i) {
@@ -3328,9 +2770,11 @@ namespace QuickMedia {
bool redraw = true;
sf::Event event;
- while (current_page == Page::CHAT_LOGIN) {
+ auto body = create_body();
+
+ while (current_page == PageType::CHAT_LOGIN) {
while (window.pollEvent(event)) {
- base_event_handler(event, Page::EXIT, false, false, false);
+ base_event_handler(event, PageType::EXIT, body.get(), nullptr, false, false);
if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) {
redraw = true;
} else if(event.type == sf::Event::TextEntered) {
@@ -3347,8 +2791,7 @@ namespace QuickMedia {
if(redraw) {
redraw = false;
- search_bar->onWindowResize(window_size);
- get_body_dimensions(window_size, search_bar.get(), body_pos, body_size);
+ get_body_dimensions(window_size, nullptr, body_pos, body_size);
}
window.clear(back_color);
@@ -3392,8 +2835,9 @@ namespace QuickMedia {
}
void Program::chat_page() {
- assert(current_plugin->name == "matrix");
- Matrix *matrix = static_cast<Matrix*>(current_plugin);
+ assert(strcmp(plugin_name, "matrix") == 0);
+
+ auto video_page = std::make_unique<MatrixVideoPage>(this);
std::vector<ChatTab> tabs;
int selected_tab = 0;
@@ -3439,7 +2883,7 @@ namespace QuickMedia {
bool is_window_focused = window.hasFocus();
- auto process_new_room_messages = [matrix, &body_items_by_room_id, &current_room_id, &is_window_focused](RoomSyncMessages &room_sync_messages, bool only_show_mentions) mutable {
+ auto process_new_room_messages = [this, &body_items_by_room_id, &current_room_id, &is_window_focused](RoomSyncMessages &room_sync_messages, bool only_show_mentions) mutable {
for(auto &[room, messages] : room_sync_messages) {
bool was_mentioned = false;
for(auto &message : messages) {
@@ -3494,7 +2938,7 @@ namespace QuickMedia {
URL_SELECTION
};
- Page new_page = Page::CHAT;
+ PageType new_page = PageType::CHAT;
ChatState chat_state = ChatState::NAVIGATING;
std::shared_ptr<BodyItem> currently_operating_on_item;
@@ -3506,7 +2950,7 @@ namespace QuickMedia {
chat_input.draw_background = false;
chat_input.set_editable(false);
- chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, &current_room_id, &new_page, &chat_state, &currently_operating_on_item](const std::string &text) mutable {
+ chat_input.on_submit_callback = [this, &chat_input, &tabs, &selected_tab, &current_room_id, &new_page, &chat_state, &currently_operating_on_item](const std::string &text) mutable {
if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
if(text.empty())
return false;
@@ -3514,12 +2958,12 @@ namespace QuickMedia {
if(chat_state == ChatState::TYPING_MESSAGE && text[0] == '/') {
std::string command = strip(text);
if(command == "/upload") {
- new_page = Page::FILE_MANAGER;
+ new_page = PageType::FILE_MANAGER;
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
return true;
} else if(command == "/logout") {
- new_page = Page::CHAT_LOGIN;
+ new_page = PageType::CHAT_LOGIN;
chat_input.set_editable(false);
chat_state = ChatState::NAVIGATING;
return true;
@@ -3622,7 +3066,7 @@ namespace QuickMedia {
auto room_avatar_thumbnail_data = std::make_shared<ThumbnailData>();
AsyncImageLoader async_image_loader;
- auto typing_async_func = [matrix](bool new_state, std::string room_id) {
+ auto typing_async_func = [this](bool new_state, std::string room_id) {
if(new_state) {
matrix->on_start_typing(room_id);
} else {
@@ -3650,17 +3094,17 @@ namespace QuickMedia {
std::future<void> set_read_marker_future;
bool setting_read_marker = false;
- auto launch_url = [this, &redraw](const std::string &url) mutable {
+ auto launch_url = [this, &video_page, &redraw](const std::string &url) mutable {
if(url.empty())
return;
std::string video_id;
if(youtube_url_extract_id(url, video_id)) {
- page_stack.push(Page::CHAT);
+ page_stack.push(PageType::CHAT);
watched_videos.clear();
- content_url = url;
- current_page = Page::VIDEO_CONTENT;
- video_content_page();
+ current_page = PageType::VIDEO_CONTENT;
+ // TODO: Add title
+ video_content_page(video_page.get(), url, "No title");
redraw = true;
} else {
if(!is_program_executable_by_name("xdg-open")) {
@@ -3710,10 +3154,10 @@ namespace QuickMedia {
return result;
};
- while (current_page == Page::CHAT) {
+ while (current_page == PageType::CHAT) {
sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
while (window.pollEvent(event)) {
- base_event_handler(event, Page::EXIT, false, false, false);
+ base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false);
if(event.type == sf::Event::GainedFocus) {
is_window_focused = true;
redraw = true;
@@ -3744,7 +3188,6 @@ namespace QuickMedia {
fetching_previous_messages_running = true;
previous_messages_future_room_id = current_room_id;
previous_messages_future = std::async(std::launch::async, [this, &previous_messages_future_room_id]() {
- Matrix *matrix = static_cast<Matrix*>(current_plugin);
BodyItems result_items;
if(matrix->get_previous_room_messages(previous_messages_future_room_id, result_items) != PluginResult::OK)
fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", previous_messages_future_room_id.c_str());
@@ -3758,9 +3201,7 @@ namespace QuickMedia {
} else if(event.key.code == sf::Keyboard::End) {
tabs[selected_tab].body->select_last_item();
} else if(event.key.code == sf::Keyboard::Escape) {
- current_page = Page::EXIT;
- body->clear_items();
- body->reset_selected();
+ current_page = PageType::EXIT;
} else if(event.key.code == sf::Keyboard::Left && synced) {
tabs[selected_tab].body->clear_thumbnails();
selected_tab = std::max(0, selected_tab - 1);
@@ -3836,11 +3277,11 @@ namespace QuickMedia {
if(!selected->url.empty()) {
const char *content_type = link_get_content_type(selected->url);
if(content_type && (strcmp(content_type, "audio") == 0 || strcmp(content_type, "video") == 0 || strcmp(content_type, "image") == 0)) {
- page_stack.push(Page::CHAT);
+ page_stack.push(PageType::CHAT);
watched_videos.clear();
- content_url = selected->url;
- current_page = Page::VIDEO_CONTENT;
- video_content_page();
+ current_page = PageType::VIDEO_CONTENT;
+ // TODO: Add title
+ video_content_page(video_page.get(), selected->url, "No title");
redraw = true;
continue;
}
@@ -3959,15 +3400,19 @@ namespace QuickMedia {
}
switch(new_page) {
- case Page::FILE_MANAGER: {
- new_page = Page::CHAT;
- if(!file_manager) {
- file_manager = new FileManager();
- file_manager->set_current_directory(get_home_dir().data);
- }
- page_stack.push(Page::CHAT);
- current_page = Page::FILE_MANAGER;
- file_manager_page();
+ case PageType::FILE_MANAGER: {
+ new_page = PageType::CHAT;
+
+ auto file_manager_page = std::make_unique<FileManagerPage>(this);
+ file_manager_page->set_current_directory(get_home_dir().data);
+ auto file_manager_body = create_body();
+ file_manager_page->get_files_in_directory(file_manager_body->items);
+ std::vector<Tab> tabs;
+ tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ selected_files.clear();
+ page_loop(std::move(tabs));
+
if(selected_files.empty()) {
fprintf(stderr, "No files selected!\n");
} else {
@@ -3982,8 +3427,8 @@ namespace QuickMedia {
redraw = true;
break;
}
- case Page::CHAT_LOGIN: {
- new_page = Page::CHAT;
+ case PageType::CHAT_LOGIN: {
+ new_page = PageType::CHAT;
matrix->logout();
tabs[MESSAGES_TAB_INDEX].body->clear_thumbnails();
// TODO: Instead of doing this, exit this current function and navigate to chat login page instead.
@@ -3991,9 +3436,9 @@ namespace QuickMedia {
// and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow.
//delete current_plugin;
//current_plugin = new Matrix();
- current_page = Page::CHAT_LOGIN;
+ current_page = PageType::CHAT_LOGIN;
chat_login_page();
- if(current_page == Page::CHAT)
+ if(current_page == PageType::CHAT)
chat_page();
exit(0);
break;
@@ -4091,8 +3536,6 @@ namespace QuickMedia {
sync_timer.restart();
sync_future_room_id = current_room_id;
sync_future = std::async(std::launch::async, [this, &sync_future_room_id, synced]() {
- Matrix *matrix = static_cast<Matrix*>(current_plugin);
-
SyncFutureResult result;
if(matrix->sync(result.room_sync_messages) == PluginResult::OK) {
fprintf(stderr, "Synced matrix\n");
@@ -4100,7 +3543,7 @@ namespace QuickMedia {
if(!synced) {
if(matrix->get_joined_rooms(result.rooms_body_items) != PluginResult::OK) {
show_notification("QuickMedia", "Failed to get a list of joined rooms", Urgency::CRITICAL);
- current_page = Page::EXIT;
+ current_page = PageType::EXIT;
return result;
}
}
@@ -4311,7 +3754,7 @@ namespace QuickMedia {
current_room_body_data->last_read_message_timestamp = message->timestamp;
// TODO: What if the message is no longer valid?
setting_read_marker = true;
- set_read_marker_future = std::async(std::launch::async, [matrix, current_room_id, message]() mutable {
+ set_read_marker_future = std::async(std::launch::async, [this, current_room_id, message]() mutable {
if(matrix->set_read_marker(current_room_id, message) != PluginResult::OK) {
fprintf(stderr, "Warning: failed to set read marker to %s\n", message->event_id.c_str());
}