aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--include/SearchBar.hpp10
-rw-r--r--plugins/Plugin.hpp2
-rw-r--r--plugins/Youtube.hpp4
-rw-r--r--src/QuickMedia.cpp9
-rw-r--r--src/SearchBar.cpp77
-rw-r--r--src/plugins/Youtube.cpp70
7 files changed, 151 insertions, 24 deletions
diff --git a/README.md b/README.md
index 2d3873e..f34ad10 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,8 @@ Press `R` to paste the post number of the selected post into the post field (ima
Press `Ctrl + C` to begin writing a post to a thread (image boards).\
Press `1 to 9` or `Numpad 1 to 9` to select google captcha image when posting a comment on 4chan.\
Press `P` to preview the attached item of the selected row in full screen view. Only works for image boards when browsing a thread.\
-Press `I` to switch between single image and scroll image view mode when reading manga.
+Press `I` to switch between single image and scroll image view mode when reading manga.\
+Press `Tab` to autocomplete a search when autocomplete is available (currently only available for youtube).\
## Video controls
Press `space` to pause/unpause video. `Double-click` video to fullscreen or leave fullscreen.
# Mangadex
diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp
index 7a6c483..6222da4 100644
--- a/include/SearchBar.hpp
+++ b/include/SearchBar.hpp
@@ -11,8 +11,8 @@ namespace QuickMedia {
using TextUpdateCallback = std::function<void(const sf::String &text)>;
// Return true to consume the search (clear the search field)
using TextSubmitCallback = std::function<bool(const sf::String &text)>;
-
using TextBeginTypingCallback = std::function<void()>;
+ using AutocompleteRequestCallback = std::function<std::string(const sf::String &text)>;
class SearchBar {
public:
@@ -24,6 +24,7 @@ namespace QuickMedia {
void clear();
void append_text(const std::string &text_to_add);
bool is_cursor_at_start_of_line() const;
+ void set_to_autocomplete();
float getBottom() const;
float getBottomWithoutShadow() const;
@@ -31,15 +32,22 @@ namespace QuickMedia {
TextUpdateCallback onTextUpdateCallback;
TextSubmitCallback onTextSubmitCallback;
TextBeginTypingCallback onTextBeginTypingCallback;
+ AutocompleteRequestCallback onAutocompleteRequestCallback;
int text_autosearch_delay;
+ int autocomplete_search_delay;
+ private:
+ void clear_autocomplete_if_text_not_substring();
+ void clear_autocomplete_if_last_char_not_substr();
private:
sf::Text text;
+ sf::Text autocomplete_text;
sf::RectangleShape background;
sf::RectangleShape background_shadow;
sf::RectangleShape shade;
sf::Sprite plugin_logo_sprite;
bool show_placeholder;
bool updated_search;
+ bool updated_autocomplete;
bool draw_logo;
bool needs_update;
sf::Clock time_since_search_update;
diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp
index be70684..54ce67d 100644
--- a/plugins/Plugin.hpp
+++ b/plugins/Plugin.hpp
@@ -65,6 +65,8 @@ namespace QuickMedia {
}
virtual bool search_suggestions_has_thumbnails() const = 0;
virtual bool search_results_has_thumbnails() const = 0;
+ virtual std::string autocomplete_search(const std::string &query) { return query; }
+ virtual int get_autocomplete_delay() const { return 100; }
virtual int get_search_delay() const = 0;
virtual bool search_suggestion_is_search() const { return false; }
virtual Page get_page_after_search() const = 0;
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index 2eea8c2..5a88970 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -10,13 +10,13 @@ namespace QuickMedia {
BodyItems get_related_media(const std::string &url) override;
bool search_suggestions_has_thumbnails() const override { return true; }
bool search_results_has_thumbnails() const override { return false; }
+ std::string autocomplete_search(const std::string &query) override;
int get_search_delay() const override { return 350; }
bool search_suggestion_is_search() const override { return true; }
Page get_page_after_search() const override { return Page::VIDEO_CONTENT; }
private:
void search_suggestions_get_continuation(const std::string &url, const std::string &continuation_token, BodyItems &result_items);
private:
- std::string last_related_media_playlist_id;
- BodyItems last_playlist_data;
+ std::string last_autocomplete_result;
};
} \ No newline at end of file
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)