aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-07-03 16:55:19 +0200
committerdec05eba <dec05eba@protonmail.com>2020-07-03 16:55:19 +0200
commit18467b4cd333ab0b7aa10b1c1acd83942c583e60 (patch)
treebeb77bd217a9530bd2ed057f67a5412ba3f8a1e3 /src
parent2d796243ad157f52d33a5b9d4f449e3845cf1649 (diff)
Add tab autocomplete for youtube
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp9
-rw-r--r--src/SearchBar.cpp77
-rw-r--r--src/plugins/Youtube.cpp70
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)