diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/QuickMedia.cpp | 9 | ||||
-rw-r--r-- | src/SearchBar.cpp | 77 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 70 |
3 files changed, 136 insertions, 20 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 6ab8c19..0845d43 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -215,7 +215,6 @@ namespace QuickMedia { case Page::SEARCH_SUGGESTION: body->draw_thumbnails = current_plugin->search_suggestions_has_thumbnails(); search_suggestion_page(); - search_bar->onTextBeginTypingCallback = nullptr; break; #if 0 case Page::SEARCH_RESULT: @@ -400,6 +399,11 @@ namespace QuickMedia { typing = true; }; + search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay(); + search_bar->onAutocompleteRequestCallback = [this](const sf::String &text) { + return current_plugin->autocomplete_search(text); + }; + search_bar->onTextUpdateCallback = [&update_search_text, this, &tabs, &selected_tab, &typing](const std::string &text) { if(tabs[selected_tab].body == body && !current_plugin->search_is_filter()) update_search_text = text; @@ -496,6 +500,8 @@ namespace QuickMedia { } else if(event.key.code == sf::Keyboard::Right) { selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1); search_bar->clear(); + } else if(event.key.code == sf::Keyboard::Tab) { + search_bar->set_to_autocomplete(); } } } @@ -575,6 +581,7 @@ namespace QuickMedia { } search_bar->onTextBeginTypingCallback = nullptr; + search_bar->onAutocompleteRequestCallback = nullptr; } void Program::search_result_page() { diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index f9b6d0e..9d8a168 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -15,14 +15,19 @@ namespace QuickMedia { onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), onTextBeginTypingCallback(nullptr), + onAutocompleteRequestCallback(nullptr), text_autosearch_delay(0), + autocomplete_search_delay(0), text("Search...", font, 18), + autocomplete_text("", font, 18), show_placeholder(true), updated_search(false), + updated_autocomplete(false), draw_logo(false), needs_update(false) { text.setFillColor(text_placeholder_color); + autocomplete_text.setFillColor(text_placeholder_color); background.setFillColor(front_color); background_shadow.setFillColor(sf::Color(23, 25, 27)); //background_shadow.setPosition(background.getPosition() + sf::Vector2f(5.0f, 5.0f)); @@ -43,20 +48,26 @@ namespace QuickMedia { window.draw(background_shadow); window.draw(shade); window.draw(background); + // TODO: Render starting from the character after text length + window.draw(autocomplete_text); window.draw(text); if(draw_logo) window.draw(plugin_logo_sprite); } void SearchBar::update() { - if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= text_autosearch_delay) { - time_since_search_update.restart(); + sf::Int32 elapsed_time = time_since_search_update.getElapsedTime().asMilliseconds(); + if(updated_search && elapsed_time >= text_autosearch_delay) { updated_search = false; sf::String str = text.getString(); if(show_placeholder) str.clear(); if(onTextUpdateCallback) onTextUpdateCallback(str); + } else if(updated_autocomplete && elapsed_time >= autocomplete_search_delay) { + updated_autocomplete = false; + if(!show_placeholder && onAutocompleteRequestCallback) + autocomplete_text.setString(onAutocompleteRequestCallback(text.getString())); } } @@ -89,7 +100,9 @@ namespace QuickMedia { background.setPosition(offset_x, padding_vertical); background_shadow.setPosition(0.0f, std::floor(shade.getSize().y)); - text.setPosition(std::floor(offset_x + background_margin_horizontal), std::floor(padding_vertical + background_margin_vertical)); + sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_vertical + background_margin_vertical)); + autocomplete_text.setPosition(font_position); + text.setPosition(font_position); } void SearchBar::onTextEntered(sf::Uint32 codepoint) { @@ -105,10 +118,14 @@ namespace QuickMedia { show_placeholder = true; text.setString("Search..."); text.setFillColor(text_placeholder_color); + autocomplete_text.setString(""); + } else { + clear_autocomplete_if_text_not_substring(); } if(!updated_search && onTextBeginTypingCallback) onTextBeginTypingCallback(); updated_search = true; + updated_autocomplete = true; time_since_search_update.restart(); } } else if(codepoint == 13) { // Return @@ -127,9 +144,11 @@ namespace QuickMedia { sf::String str = text.getString(); str += codepoint; text.setString(str); + clear_autocomplete_if_last_char_not_substr(); if(!updated_search && onTextBeginTypingCallback) onTextBeginTypingCallback(); updated_search = true; + updated_autocomplete = true; time_since_search_update.restart(); } else if(codepoint == '\n') needs_update = true; @@ -141,8 +160,10 @@ namespace QuickMedia { show_placeholder = true; text.setString("Search..."); text.setFillColor(text_placeholder_color); + autocomplete_text.setString(""); needs_update = true; updated_search = false; + updated_autocomplete = false; } void SearchBar::append_text(const std::string &text_to_add) { @@ -154,9 +175,11 @@ namespace QuickMedia { sf::String str = text.getString(); str += text_to_add; text.setString(str); + clear_autocomplete_if_text_not_substring(); if(!updated_search && onTextBeginTypingCallback) onTextBeginTypingCallback(); updated_search = true; + updated_autocomplete = true; time_since_search_update.restart(); needs_update = true; } @@ -167,6 +190,54 @@ namespace QuickMedia { 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 && onTextBeginTypingCallback) + onTextBeginTypingCallback(); + updated_search = true; + updated_autocomplete = true; + time_since_search_update.restart(); + needs_update = true; + } + } + + 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 shade.getSize().y + background_shadow.getSize().y; } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index c0180d8..3ab405c 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -3,6 +3,60 @@ #include <string.h> namespace QuickMedia { + static void iterate_suggestion_result(const Json::Value &value, std::vector<std::string> &result_items, int &iterate_count) { + ++iterate_count; + if(value.isArray()) { + for(const Json::Value &child : value) { + iterate_suggestion_result(child, result_items, iterate_count); + } + } else if(value.isString() && iterate_count > 2) { + result_items.push_back(value.asString()); + } + } + + std::string Youtube::autocomplete_search(const std::string &query) { + // Return the last result if the query is a substring of the autocomplete result + if(last_autocomplete_result.size() >= query.size() && memcmp(query.data(), last_autocomplete_result.data(), query.size()) == 0) + return last_autocomplete_result; + + std::string url = "https://clients1.google.com/complete/search?client=youtube&hl=en&gs_rn=64&gs_ri=youtube&ds=yt&cp=7&gs_id=x&q="; + url += url_param_encode(query); + + std::string server_response; + if(download_to_string(url, server_response, {}, use_tor, true) != DownloadResult::OK) + return query; + + size_t json_start = server_response.find_first_of('('); + if(json_start == std::string::npos) + return query; + ++json_start; + + size_t json_end = server_response.find_last_of(')'); + if(json_end == std::string::npos) + return query; + + if(json_end == 0 || json_start >= json_end) + return query; + + 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(&server_response[json_start], &server_response[json_end], &json_root, &json_errors)) { + fprintf(stderr, "Youtube autocomplete search json error: %s\n", json_errors.c_str()); + return query; + } + + int iterate_count = 0; + std::vector<std::string> result_items; + iterate_suggestion_result(json_root, result_items, iterate_count); + if(result_items.empty()) + return query; + + last_autocomplete_result = result_items[0]; + return result_items[0]; + } + static size_t find_end_of_json(const std::string &website_data, size_t data_start) { int brace_count = 0; char string_char = '\0'; @@ -228,14 +282,6 @@ namespace QuickMedia { } } - static std::string get_playlist_id_from_url(const std::string &url) { - std::string playlist_id = url; - size_t list_index = playlist_id.find("&list="); - if(list_index == std::string::npos) - return playlist_id; - return playlist_id.substr(list_index); - } - static std::string remove_index_from_playlist_url(const std::string &url) { std::string result = url; size_t index = result.rfind("&index="); @@ -280,14 +326,6 @@ namespace QuickMedia { BodyItems result_items; std::string modified_url = remove_index_from_playlist_url(url); - std::string playlist_id = get_playlist_id_from_url(modified_url); - if(playlist_id == last_related_media_playlist_id) { - result_items.reserve(last_playlist_data.size()); - for(auto &data : last_playlist_data) { - result_items.push_back(std::make_unique<BodyItem>(*data)); - } - return result_items; - } std::string website_data; if(download_to_string(modified_url, website_data, {}, use_tor, true) != DownloadResult::OK) |