diff options
-rw-r--r-- | include/Body.hpp | 1 | ||||
-rw-r--r-- | include/QuickMedia.hpp | 1 | ||||
-rw-r--r-- | include/SearchBar.hpp | 10 | ||||
-rw-r--r-- | plugins/MediaGeneric.hpp | 2 | ||||
-rw-r--r-- | plugins/Page.hpp | 11 | ||||
-rw-r--r-- | plugins/Youtube.hpp | 7 | ||||
-rw-r--r-- | src/Body.cpp | 18 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 64 | ||||
-rw-r--r-- | src/SearchBar.cpp | 86 | ||||
-rw-r--r-- | src/plugins/MediaGeneric.cpp | 8 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 139 |
11 files changed, 171 insertions, 176 deletions
diff --git a/include/Body.hpp b/include/Body.hpp index b44a386..23439ec 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -69,7 +69,6 @@ namespace QuickMedia { void append_item(std::shared_ptr<BodyItem> body_item); void append_items(BodyItems new_items); void insert_item(std::shared_ptr<BodyItem> body_item, int index); - void move_items_to(Body *other_body); void move_item(size_t src_index, size_t dst_index); // Returns the inserted position size_t insert_item_by_timestamp(std::shared_ptr<BodyItem> body_item); diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 4399c3d..71e4797 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -68,6 +68,7 @@ namespace QuickMedia { bool typing = false; bool fetching_next_page_running = false; bool fetching_next_page_failed = false; + bool search_suggestion_submitted = false; int fetched_page = 0; sf::Text search_result_text; AsyncTask<FetchResult> fetch_future; diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp index 027f534..a888ffb 100644 --- a/include/SearchBar.hpp +++ b/include/SearchBar.hpp @@ -18,7 +18,6 @@ namespace QuickMedia { using TextUpdateCallback = std::function<void(const std::string &text)>; using TextSubmitCallback = std::function<void(const std::string &text)>; using TextBeginTypingCallback = std::function<void()>; - using AutocompleteRequestCallback = std::function<void(const std::string &text)>; class SearchBar { public: @@ -29,9 +28,6 @@ namespace QuickMedia { void onWindowResize(const sf::Vector2f &window_size); void clear(); void append_text(const std::string &text_to_add); - bool is_cursor_at_start_of_line() const; - void set_to_autocomplete(); - void set_autocomplete_text(const std::string &text); void set_position(sf::Vector2f pos); void set_editable(bool editable); @@ -45,9 +41,7 @@ namespace QuickMedia { TextUpdateCallback onTextUpdateCallback; TextSubmitCallback onTextSubmitCallback; TextBeginTypingCallback onTextBeginTypingCallback; - AutocompleteRequestCallback onAutocompleteRequestCallback; int text_autosearch_delay; - int autocomplete_search_delay; bool caret_visible; float padding_top = 0.0f; @@ -55,11 +49,8 @@ namespace QuickMedia { float padding_x = 10.0f; private: void onTextEntered(sf::Uint32 codepoint); - void clear_autocomplete_if_text_not_substring(); - void clear_autocomplete_if_last_char_not_substr(); private: sf::Text text; - sf::Text autocomplete_text; RoundedRectangle background; sf::RectangleShape shade; sf::RectangleShape caret; @@ -67,7 +58,6 @@ namespace QuickMedia { std::string placeholder_str; bool show_placeholder; bool updated_search; - bool updated_autocomplete; bool draw_logo; bool needs_update; bool input_masked; diff --git a/plugins/MediaGeneric.hpp b/plugins/MediaGeneric.hpp index b1f9030..8885db2 100644 --- a/plugins/MediaGeneric.hpp +++ b/plugins/MediaGeneric.hpp @@ -84,7 +84,7 @@ namespace QuickMedia { MediaGenericVideoPage(Program *program, MediaGenericSearchPage *search_page, const std::string &url) : VideoPage(program, url), search_page(search_page) {} const char* get_title() const override { return ""; } BodyItems get_related_media(const std::string &url) override; - std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override; + bool create_search_page(Program *program, Tab &tab) override; std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program) override; std::unique_ptr<Page> create_channels_page(Program*, const std::string&) override { return nullptr; diff --git a/plugins/Page.hpp b/plugins/Page.hpp index c61fd6d..9d5bade 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -29,6 +29,9 @@ namespace QuickMedia { virtual bool search_is_filter() { return true; } // This show be overriden if search_is_filter is overriden to return false virtual SearchResult search(const std::string &str, BodyItems &result_items) { (void)str; (void)result_items; return SearchResult::ERR; } + // If this returns true then |submit_suggestion| is called when submitting the selected item instead of |submit| + // and |submit| is called when submitting the response of |submit_suggestion| + virtual bool search_is_suggestion() { return false; } // Return empty |result_tabs| and PluginResult::OK to do nothing; which is useful for implementing custom actions on item submit virtual PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { @@ -40,6 +43,12 @@ namespace QuickMedia { // Override and return false to make submit run in the main (ui) thread virtual bool submit_is_async() { return true; } virtual bool clear_search_after_submit() { return false; } + virtual PluginResult submit_suggestion(const std::string &title, const std::string &url, BodyItems &result_items) { + (void)title; + (void)url; + (void)result_items; + return PluginResult::ERR; + } // Note: If pagination is done by fetching the next page until we get to |page|, then the "current page" should be reset everytime |search| is called. // Note: the first page is 0 virtual PluginResult get_page(const std::string &str, int page, BodyItems &result_items) { (void)str; (void)page; (void)result_items; return PluginResult::OK; } @@ -110,7 +119,7 @@ namespace QuickMedia { virtual PageTypez get_type() const override { return PageTypez::VIDEO; } virtual bool autoplay_next_item() { return false; } virtual BodyItems get_related_media(const std::string &url) { (void)url; return {}; } - virtual std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) { (void)program; (void)search_delay; return nullptr; } + virtual bool create_search_page(Program *program, Tab &tab) { (void)program; (void)tab; return false; } virtual std::unique_ptr<Page> create_comments_page(Program *program) { (void)program; return nullptr; } // Return nullptr if the service doesn't support related videos page virtual std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program) = 0; diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index 50412de..c7aff93 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -30,17 +30,20 @@ namespace QuickMedia { class YoutubeSearchPage : public LazyFetchPage { public: - YoutubeSearchPage(Program *program) : LazyFetchPage(program) {} + YoutubeSearchPage(Program *program, std::string video_id = "") : LazyFetchPage(program), video_id(std::move(video_id)) {} const char* get_title() const override { return "Search"; } + bool search_is_suggestion() override { return true; } bool search_is_filter() override { return false; } SearchResult search(const std::string &str, BodyItems &result_items) override; PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + PluginResult submit_suggestion(const std::string &title, const std::string &url, BodyItems &result_items) override; PluginResult lazy_fetch(BodyItems &result_items) override; bool lazy_fetch_is_loader() override { return true; } private: PluginResult search_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items); private: + std::string video_id; std::string search_url; std::string continuation_token; int current_page = 0; @@ -138,7 +141,7 @@ namespace QuickMedia { YoutubeVideoPage(Program *program, std::string url); const char* get_title() const override { return ""; } BodyItems get_related_media(const std::string &url) override; - std::unique_ptr<Page> create_search_page(Program *program, int &search_delay) override; + bool create_search_page(Program *program, Tab &tab) override; std::unique_ptr<Page> create_comments_page(Program *program) override; std::unique_ptr<RelatedVideosPage> create_related_videos_page(Program *program) override; std::unique_ptr<Page> create_channels_page(Program *program, const std::string &channel_url) override; diff --git a/src/Body.cpp b/src/Body.cpp index 766542a..c3cbcda 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -261,6 +261,11 @@ namespace QuickMedia { filter_search_fuzzy_item(current_filter, item.get()); } this->items = std::move(items); + if(attach_side == AttachSide::TOP) { + selected_item = 0; + prev_selected_item = selected_item; + page_scroll = 0.0f; + } } void Body::clear_items() { @@ -299,10 +304,6 @@ namespace QuickMedia { items.insert(items.begin() + index, std::move(body_item)); } - void Body::move_items_to(Body *other_body) { - other_body->set_items(std::move(items)); - } - void Body::move_item(size_t src_index, size_t dst_index) { assert(src_index < items.size()); assert(dst_index < items.size()); @@ -1661,11 +1662,20 @@ namespace QuickMedia { } void Body::filter_search_fuzzy_item(const std::string &text, BodyItem *body_item) { + const bool prev_visible = body_item->visible; + body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_title(), text); if(!body_item->visible && !body_item->get_description().empty()) body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_description(), text); if(!body_item->visible && !body_item->get_author().empty()) body_item->visible = string_find_fuzzy_case_insensitive(body_item->get_author(), text); + + if(prev_visible && !body_item->visible) { + clear_body_item_cache(body_item); + // TODO: Make sure the embedded item is not referencing another item in the |items| list + if(body_item->embedded_item) + clear_body_item_cache(body_item->embedded_item.get()); + } } bool Body::no_items_visible() const { diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 52452d4..dc23892 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1222,7 +1222,7 @@ namespace QuickMedia { if(youtube_url.empty()) { start_tab_index = 1; tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeSubscriptionsPage>(this), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); + tabs.push_back(Tab{create_body(false, false), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 100)}); auto recommended_page = std::make_unique<YoutubeRecommendedPage>(this); tabs.push_back(Tab{create_body(false, true), std::move(recommended_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); @@ -1708,9 +1708,6 @@ namespace QuickMedia { tab_associated_data.push_back(std::move(data)); } - //std::string autocomplete_text; - //bool autocomplete_running = false; - double gradient_inc = 0.0; const float gradient_height = 5.0f; @@ -1733,12 +1730,19 @@ namespace QuickMedia { hide_virtual_keyboard(); std::vector<Tab> new_tabs; + BodyItems new_body_items; + const bool search_suggestion_submitted = tab_associated_data[selected_tab].search_suggestion_submitted; auto prev_selected_item = tabs[selected_tab].page->submit_body_item; tabs[selected_tab].page->submit_body_item = selected_item; - auto plugin_submit_handler = [&tabs, selected_tab, &selected_item, &search_text, &new_tabs]() { - PluginResult plugin_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_tabs); - return plugin_result == PluginResult::OK; + auto plugin_submit_handler = [&tabs, selected_tab, &selected_item, &search_text, &new_tabs, &new_body_items, search_suggestion_submitted]() { + if(tabs[selected_tab].page->search_is_suggestion() && !search_suggestion_submitted) { + PluginResult plugin_result = tabs[selected_tab].page->submit_suggestion(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_body_items); + return plugin_result == PluginResult::OK; + } else { + PluginResult plugin_result = tabs[selected_tab].page->submit(selected_item ? selected_item->get_title() : search_text, selected_item ? selected_item->url : search_text, new_tabs); + return plugin_result == PluginResult::OK; + } }; TaskResult submit_result; @@ -1769,11 +1773,11 @@ namespace QuickMedia { } } - if(tabs[selected_tab].page->is_single_page()) { + if(tabs[selected_tab].page->is_single_page() && !tabs[selected_tab].page->search_is_suggestion()) { if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->clear(); if(new_tabs.size() == 1 && !new_tabs[0].page) { tabs[selected_tab].body = std::move(new_tabs[0].body); - tabs[selected_tab].page->submit_body_item = prev_selected_item; + tabs[selected_tab].page->submit_body_item = nullptr; return; } else if(new_tabs.empty()) { loop_running = false; @@ -1782,6 +1786,15 @@ namespace QuickMedia { } } + if(tabs[selected_tab].page->search_is_suggestion() && !search_suggestion_submitted) { + tabs[selected_tab].body->set_items(std::move(new_body_items)); + tabs[selected_tab].page->submit_body_item = nullptr; + tab_associated_data[selected_tab].search_suggestion_submitted = true; + if(tabs[selected_tab].search_bar) + tabs[selected_tab].search_bar->clear(); + return; + } + if(new_tabs.empty()) { tabs[selected_tab].page->submit_body_item = prev_selected_item; return; @@ -1909,6 +1922,7 @@ namespace QuickMedia { && !tab_associated_data[selected_tab].fetching_next_page_failed && (!tabs[selected_tab].search_bar || !tabs[selected_tab].page->search_is_filter() || tabs[selected_tab].search_bar->is_empty()) && tabs[selected_tab].body->get_num_visible_items() > 0 + && (!tabs[selected_tab].page->search_is_suggestion() || tab_associated_data[selected_tab].search_suggestion_submitted) && tabs[selected_tab].page->is_ready() && (!tabs[selected_tab].page->is_lazy_fetch_page() || tab_associated_data[selected_tab].lazy_fetch_finished)) { @@ -1940,12 +1954,6 @@ namespace QuickMedia { TabAssociatedData &associated_data = tab_associated_data[i]; if(tab.search_bar) { - // 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; @@ -2000,8 +2008,6 @@ namespace QuickMedia { else if(event.type == sf::Event::KeyPressed) { if(event.key.code == sf::Keyboard::Escape) { return false; - } else if(event.key.code == sf::Keyboard::Tab) { - 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) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); @@ -2162,11 +2168,13 @@ namespace QuickMedia { if(associated_data.search_text_updated && associated_data.fetch_status == FetchStatus::NONE && !associated_data.fetching_next_page_running) { std::string update_search_text = associated_data.update_search_text; + if(!tabs[i].page->search_is_suggestion() || associated_data.search_suggestion_submitted) + tabs[i].body->clear_items(); associated_data.search_text_updated = false; - tabs[i].body->clear_items(); associated_data.fetch_status = FetchStatus::LOADING; associated_data.fetch_type = FetchType::SEARCH; associated_data.search_result_text.setString("Searching..."); + associated_data.search_suggestion_submitted = false; Page *page = tabs[i].page.get(); associated_data.fetch_future = AsyncTask<FetchResult>([update_search_text, page]() { FetchResult fetch_result; @@ -2937,18 +2945,18 @@ namespace QuickMedia { window.setMouseCursorVisible(true); cursor_visible = true; - int search_delay = 0; - auto search_page = video_page->create_search_page(this, search_delay); + Tab search_page_tab; + const bool search_page_created = video_page->create_search_page(this, search_page_tab); auto comments_page = video_page->create_comments_page(this); auto related_videos_page = video_page->create_related_videos_page(this); auto channels_page = video_page->create_channels_page(this, channel_url); - if(search_page || related_videos_page || channels_page) { + if(search_page_created || related_videos_page || channels_page) { XUnmapWindow(disp, video_player_window); XSync(disp, False); std::vector<Tab> tabs; - if(search_page) { - tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", search_delay)}); + if(search_page_created) { + tabs.push_back(std::move(search_page_tab)); } if(comments_page) { tabs.push_back(Tab{create_body(), std::move(comments_page), nullptr}); @@ -4755,12 +4763,15 @@ namespace QuickMedia { //message->related_event_id.clear(); //message->related_event_type = RelatedEventType::NONE; Message *original_message = static_cast<Message*>(body_item->userdata); - if(original_message) { + if(original_message && !is_system_message_type(original_message->type)) { body_item->thumbnail_url = current_room->get_user_avatar_url(original_message->user); body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; } body_item->set_description("Message deleted"); - body_item->set_description_color(get_current_theme().text_color); + if(original_message && is_system_message_type(original_message->type)) + body_item->set_description_color(get_current_theme().faded_text_color); + else + body_item->set_description_color(get_current_theme().text_color); body_item->thumbnail_size = AVATAR_THUMBNAIL_SIZE; body_item->url.clear(); }; @@ -6995,8 +7006,7 @@ namespace QuickMedia { if(task_result == TaskResult::TRUE) { if(!new_tabs.empty()) { - new_tabs[0].body->move_items_to(file_manager_body.get()); - file_manager_body->select_first_item(); + file_manager_body = std::move(new_tabs[0].body); search_bar->clear(); } } else if(task_result == TaskResult::FALSE) { diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 5909635..c1f9ceb 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -21,17 +21,13 @@ namespace QuickMedia { onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), - onAutocompleteRequestCallback(nullptr), text_autosearch_delay(50), - autocomplete_search_delay(250), caret_visible(true), text(placeholder, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), - autocomplete_text("", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16 * get_ui_scale())), background(sf::Vector2f(1.0f, 1.0f), 10.0f, get_current_theme().selected_color, rounded_rectangle_shader), placeholder_str(placeholder), show_placeholder(true), updated_search(false), - updated_autocomplete(false), draw_logo(false), needs_update(true), input_masked(input_masked), @@ -44,7 +40,6 @@ namespace QuickMedia { padding_top = padding_top_default; padding_bottom = padding_bottom_default; text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setFillColor(get_current_theme().placeholder_text_color); shade.setFillColor(get_current_theme().shade_color); if(plugin_logo && plugin_logo->getNativeHandle() != 0) plugin_logo_sprite.setTexture(*plugin_logo, true); @@ -65,8 +60,7 @@ namespace QuickMedia { window.draw(shade); background.draw(window); - // TODO: Render starting from the character after text length - window.draw(autocomplete_text); + if(input_masked && !show_placeholder) { std::string masked_str(text.getString().getSize(), '*'); sf::Text masked_text(std::move(masked_str), *text.getFont(), text.getCharacterSize()); @@ -117,7 +111,6 @@ namespace QuickMedia { if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::D && event.key.control) { clear(); updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); } @@ -144,13 +137,6 @@ namespace QuickMedia { if(onTextUpdateCallback) onTextUpdateCallback(*u8_str); typing = false; - } else if(updated_autocomplete && elapsed_time >= autocomplete_search_delay) { - updated_autocomplete = false; - if(!show_placeholder && onAutocompleteRequestCallback) { - auto u8 = text.getString().toUtf8(); - std::string *u8_str = (std::string*)&u8; - onAutocompleteRequestCallback(*u8_str); - } } } @@ -184,7 +170,6 @@ namespace QuickMedia { background.set_position(sf::Vector2f(pos.x + offset_x, pos.y + padding_top)); shade.setPosition(pos); sf::Vector2f font_position(std::floor(pos.x + offset_x + background_margin_horizontal), std::floor(pos.y + padding_top + background_margin_vertical)); - autocomplete_text.setPosition(font_position); text.setPosition(font_position); } @@ -201,9 +186,6 @@ namespace QuickMedia { show_placeholder = true; text.setString(placeholder_str); text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setString(""); - } else { - clear_autocomplete_if_text_not_substring(); } if(!updated_search) { typing = true; @@ -211,7 +193,6 @@ namespace QuickMedia { onTextBeginTypingCallback(); } updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); } } else if(codepoint == 13) { // Return @@ -235,10 +216,8 @@ namespace QuickMedia { show_placeholder = true; text.setString(placeholder_str); text.setFillColor(get_current_theme().placeholder_text_color); - autocomplete_text.setString(""); needs_update = true; updated_search = false; - updated_autocomplete = false; backspace_pressed = false; } @@ -256,51 +235,18 @@ namespace QuickMedia { str += text_to_add; text.setString(str); - clear_autocomplete_if_text_not_substring(); if(!updated_search) { typing = true; if(onTextBeginTypingCallback) onTextBeginTypingCallback(); } updated_search = true; - updated_autocomplete = true; time_since_search_update.restart(); backspace_pressed = false; if(text_to_add.find('\n') != std::string::npos) needs_update = true; } - bool SearchBar::is_cursor_at_start_of_line() const { - // TODO: When it's possible to move the cursor, then check at the cursor position instead of end of the string - const sf::String &str = text.getString(); - return show_placeholder || str.getSize() == 0 || str[str.getSize() - 1] == '\n'; - } - - void SearchBar::set_to_autocomplete() { - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(!autocomplete_str.isEmpty()) { - if(show_placeholder) { - show_placeholder = false; - text.setString(""); - text.setFillColor(sf::Color::White); - } - text.setString(autocomplete_str); - if(!updated_search) { - typing = true; - if(onTextBeginTypingCallback) - onTextBeginTypingCallback(); - } - updated_search = true; - updated_autocomplete = true; - time_since_search_update.restart(); - needs_update = true; - } - } - - void SearchBar::set_autocomplete_text(const std::string &text) { - autocomplete_text.setString(text); - } - void SearchBar::set_position(sf::Vector2f pos) { if(std::abs(this->pos.x - pos.x) > 1.0f || std::abs(this->pos.y - pos.y) > 1.0f) { this->pos = pos; @@ -316,36 +262,6 @@ namespace QuickMedia { return editable; } - void SearchBar::clear_autocomplete_if_text_not_substring() { - const sf::String &text_str = text.getString(); - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(text_str.getSize() > autocomplete_str.getSize()) { - autocomplete_text.setString(""); - return; - } - - for(size_t i = 0; i < autocomplete_str.getSize(); ++i) { - if(text_str[i] != autocomplete_str[i]) { - autocomplete_text.setString(""); - return; - } - } - } - - void SearchBar::clear_autocomplete_if_last_char_not_substr() { - const sf::String &text_str = text.getString(); - const sf::String &autocomplete_str = autocomplete_text.getString(); - if(text_str.isEmpty() || text_str.getSize() > autocomplete_str.getSize()) { - autocomplete_text.setString(""); - return; - } - - if(autocomplete_str[text_str.getSize() - 1] != text_str[text_str.getSize() - 1]) { - autocomplete_text.setString(""); - return; - } - } - float SearchBar::getBottom() const { return getBottomWithoutShadow() + 5.0f;//background_shadow.getSize().y; } diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp index c829a33..d536a09 100644 --- a/src/plugins/MediaGeneric.cpp +++ b/src/plugins/MediaGeneric.cpp @@ -191,9 +191,11 @@ namespace QuickMedia { return result_items; } - std::unique_ptr<Page> MediaGenericVideoPage::create_search_page(Program*, int &search_delay) { - search_delay = 500; // TODO: Make configurable? - return std::make_unique<MediaGenericSearchPage>(*search_page); + bool MediaGenericVideoPage::create_search_page(Program*, Tab &tab) { + tab.body = create_body(false, true); + tab.page = std::make_unique<MediaGenericSearchPage>(*search_page); + tab.search_bar = create_search_bar("Search...", 500); // TODO: Make search delay configurable? + return true; } std::unique_ptr<RelatedVideosPage> MediaGenericVideoPage::create_related_videos_page(Program *program) { diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index ea72f03..5b0591c 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -764,12 +764,98 @@ namespace QuickMedia { } SearchResult YoutubeSearchPage::search(const std::string &str, BodyItems &result_items) { + if(str.empty()) + return SearchResult::OK; + + // TODO: Find this search url from youtube.com/... searchbox.js, and the url to that script from youtube.com/ html + std::string url = "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&hl=en&gl=us&sugexp=rdcfrc%2Ccfro%3D1%2Cfp.cfr%3D1&gs_rn=64&gs_ri=youtube&ds=yt&cp=4&gs_id=f&xhr=t&xssi=t&q="; + url += url_param_encode(str); + if(!video_id.empty()) + url += "&video_id=" + video_id; + + std::vector<CommandArg> additional_args = { + { "-H", "origin: https://www.youtube.com" }, + { "-H", "referer: https://www.youtube.com/" } + }; + + std::vector<CommandArg> cookies = get_cookies(); + additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); + + std::string website_data; + DownloadResult result = download_to_string(url, website_data, std::move(additional_args), true); + if(result != DownloadResult::OK) return download_result_to_search_result(result); + + const size_t json_start_index = website_data.find('['); + if(json_start_index == std::string::npos) + return SearchResult::ERR; + + Json::Value json_root; + Json::CharReaderBuilder json_builder; + std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader()); + std::string json_errors; + if(!json_reader->parse(&website_data[json_start_index], &website_data[website_data.size()], &json_root, &json_errors)) { + fprintf(stderr, "youtube search error: %s\n", json_errors.c_str()); + return SearchResult::ERR; + } + + if(!json_root.isArray() || json_root.size() < 2) + return SearchResult::ERR; + + const Json::Value &search_result_list_json = json_root[1]; + if(!search_result_list_json.isArray()) + return SearchResult::ERR; + + for(const Json::Value &json_item : search_result_list_json) { + if(!json_item.isArray() || json_item.size() == 0) + continue; + + const Json::Value &search_result_json = json_item[0]; + if(!search_result_json.isString()) + continue; + + auto body_item = BodyItem::create(search_result_json.asString()); + body_item->url = body_item->get_title(); + result_items.push_back(std::move(body_item)); + } + + if(result_items.empty() || !strcase_equals(str.c_str(), result_items.front()->get_title().c_str())) { + auto body_item = BodyItem::create(str); + body_item->url = str; + result_items.insert(result_items.begin(), std::move(body_item)); + } + + return SearchResult::OK; + } + + PluginResult YoutubeSearchPage::get_page(const std::string&, int page, BodyItems &result_items) { + while(current_page < page) { + PluginResult plugin_result = search_get_continuation(search_url, continuation_token, result_items); + if(plugin_result != PluginResult::OK) return plugin_result; + ++current_page; + } + return PluginResult::OK; + } + + PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + if(url.empty()) + return PluginResult::OK; + + if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { + // TODO: Make all pages (for all services) lazy fetch in a similar manner! + result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)}); + } else { + result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr}); + } + return PluginResult::OK; + } + + PluginResult YoutubeSearchPage::submit_suggestion(const std::string&, const std::string &url, BodyItems &result_items) { continuation_token.clear(); current_page = 0; added_videos.clear(); search_url = "https://www.youtube.com/results?search_query="; - search_url += url_param_encode(str); + search_url += url_param_encode(url); std::vector<CommandArg> additional_args = { { "-H", "x-spf-referer: " + search_url }, @@ -783,10 +869,10 @@ namespace QuickMedia { Json::Value json_root; DownloadResult result = download_json(json_root, search_url + "&pbj=1&gl=US&hl=en", std::move(additional_args), true); - if(result != DownloadResult::OK) return download_result_to_search_result(result); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); if(!json_root.isArray()) - return SearchResult::ERR; + return PluginResult::ERR; for(const Json::Value &json_item : json_root) { if(!json_item.isObject()) @@ -811,28 +897,6 @@ namespace QuickMedia { parse_section_list_renderer(primary_contents_json["sectionListRenderer"], continuation_token, result_items, added_videos); } - return SearchResult::OK; - } - - PluginResult YoutubeSearchPage::get_page(const std::string&, int page, BodyItems &result_items) { - while(current_page < page) { - PluginResult plugin_result = search_get_continuation(search_url, continuation_token, result_items); - if(plugin_result != PluginResult::OK) return plugin_result; - ++current_page; - } - return PluginResult::OK; - } - - PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - if(url.empty()) - return PluginResult::OK; - - if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { - // TODO: Make all pages (for all services) lazy fetch in a similar manner! - result_tabs.push_back(Tab{create_body(false, true), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)}); - } else { - result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr}); - } return PluginResult::OK; } @@ -2061,9 +2125,14 @@ namespace QuickMedia { return result_items; } - std::unique_ptr<Page> YoutubeVideoPage::create_search_page(Program *program, int &search_delay) { - search_delay = 350; - return std::make_unique<YoutubeSearchPage>(program); + bool YoutubeVideoPage::create_search_page(Program *program, Tab &tab) { + std::string video_id; + youtube_url_extract_id(url, video_id); + + tab.body = create_body(false, false); + tab.page = std::make_unique<YoutubeSearchPage>(program, std::move(video_id)); + tab.search_bar = create_search_bar("Search...", 100); + return true; } std::unique_ptr<Page> YoutubeVideoPage::create_comments_page(Program *program) { @@ -2095,20 +2164,6 @@ namespace QuickMedia { return result; } - static std::string url_extract_param(const std::string &url, const std::string ¶m) { - std::string param_s = param + "="; - size_t index = url.find(param_s); - if(index == std::string::npos) - return ""; - - index += param_s.size(); - size_t end = url.find('&', index); - if(end == std::string::npos) - end = url.size(); - - return url.substr(index, end - index); - } - static const YoutubeVideoFormat* get_highest_resolution_mp4_non_av1(const std::vector<YoutubeVideoFormat> &video_formats, int max_height) { for(const YoutubeVideoFormat &video_format : video_formats) { if(video_format.height <= max_height && video_format.base.mime_type.find("mp4") != std::string::npos && video_format.base.mime_type.find("av01") == std::string::npos) |