From 1216d5840fd5fc7cfcac6164a8974dca431ecac8 Mon Sep 17 00:00:00 2001 From: trilader Date: Thu, 22 Jul 2021 00:03:20 +0200 Subject: [PATCH] Add edit support for channel filters --- application.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++--- db.cpp | 14 +++++ yt.cpp | 52 ++++++++++++++++- yt.h | 6 +- 4 files changed, 206 insertions(+), 10 deletions(-) diff --git a/application.cpp b/application.cpp index 3470f55..d73d4f8 100644 --- a/application.cpp +++ b/application.cpp @@ -533,6 +533,7 @@ void action_add_new_user_flag() { void action_rename_user_flag() { std::vector names; + names.resize(userFlags.size()); for(const UserFlag &flag: userFlags) { names.push_back(flag.name); } @@ -547,17 +548,16 @@ void action_rename_user_flag() { flag.save(db); } +constexpr const char userflag_keys[] = "1234567890" + "abcdefghij" + "klmnopqrst" + "uv"; +static_assert(sizeof(userflag_keys) == UserFlag::max_flag_count + 1, "There must be a key for each UserFlag"); // +1 to accomodate for the 0 byte at the end +const std::string userflag_keys_str(userflag_keys); + void action_manage_user_flags() { bool done = false; - constexpr const char userflag_keys[] = "1234567890" - "abcdefghij" - "klmnopqrst" - "uv"; - const std::string userflag_keys_str(userflag_keys); - // +1 to accomodate for the 0 byte at the end - static_assert(sizeof(userflag_keys) == UserFlag::max_flag_count + 1, "There must be a key for each UserFlag"); - size_t channel_name_width = 0; for(const Channel &c: channels) { channel_name_width = std::max(channel_name_width, c.tui_name_width); @@ -632,6 +632,128 @@ void action_manage_user_flags() { } while (!done); } +void edit_channel_filter(ChannelFilter &filter) { + bool done = false; + + size_t max_flag_name_width = std::max(string_width("Watched"), string_width("Downloaded")); + for(const UserFlag &flag: userFlags) { + max_flag_name_width = std::max(max_flag_name_width, string_width(flag.name)); + } + const size_t space_per_column = 3+max_flag_name_width+1; // " x Flag name " + + std::vector actions = { + {EV_IGNORE, "F3", 0, nullptr, "Rename channel filter"}, + {TERMPAINT_EV_KEY, "Escape", 0, [&]{ done = true; }, "Stop channel filter editing"}, + {EV_IGNORE, ".", 0, nullptr, "Toggle \"Downloaded\" flag for selected filter"}, + {EV_IGNORE, ",", 0, nullptr, "Toggle \"Watched\" flag for selected filter"}, + {EV_IGNORE, "1..0,a..v", 0, nullptr, "Toggle user flags for selected filter"}, + }; + + const auto draw_flag = [&](int x, int y, const char key, const std::string &name, bool active, bool value) { + char key_buf[2] = {key, 0}; + termpaint_attr *attr = get_attr(active ? ASUnwatched : ASWatched, active && !value); + termpaint_surface_write_with_attr(surface, x + 0, y, key_buf, attr); + termpaint_surface_write_with_attr(surface, x + 2, y, name.c_str(), attr); + }; + + const auto toggle_flag = [&](uint32_t &mask, uint32_t &value, unsigned int flag) { + // 0,0 -> 1,1 -> 1,0 -> ... + if((mask & flag) == 0 && (value & flag) == 0) { + mask |= flag; + value |= flag; + } else if(mask & flag && value & flag) { + value &= ~flag; + } else { + mask &= ~flag; + value &= ~flag; + } + }; + + do { + const size_t filer_name_width = string_width(filter.name); + const size_t content_rows_needed = 1+1+UserFlag::max_flag_count/2; + const size_t box_cols = 1 + 1 + std::max(2*space_per_column, filer_name_width) + 1; + const size_t box_rows = 1 + content_rows_needed + 1; + + const size_t filter_name_pos = 1; + const size_t divider_pos = box_cols / 2; + const size_t flag_pos = 2; + + draw_box_with_caption(0, 0, box_cols, box_rows); + termpaint_surface_write_with_attr(surface, filter_name_pos, 1, filter.name.c_str(), get_attr(ASNormal)); + draw_flag(flag_pos, 2, ',', "Watched", filter.video_mask & kWatched, filter.video_value & kWatched); + draw_flag(divider_pos+flag_pos, 2, '.', "Downloaded", filter.video_mask & kDownloaded, filter.video_value & kDownloaded); + + for(size_t i=0; itype == TERMPAINT_EV_CHAR && event->string.length() == 1) { + const char ch(event->string[0]); + const size_t index = userflag_keys_str.find(ch); + if(index == std::string::npos) { + if(ch == ',') { + toggle_flag(filter.video_mask, filter.video_value, kWatched); + filter.save(db); + } else if(ch == '.') { + toggle_flag(filter.video_mask, filter.video_value, kDownloaded); + filter.save(db); + } else { + continue; + } + } + + auto flag = std::find_if(userFlags.cbegin(), userFlags.cend(), [&](const UserFlag &f) { return f.id == (1<id); + filter.save(db); + } else if(event->type == TERMPAINT_EV_KEY && event->string == "F3") { + std::string name = edit_string("Enter new name", std::string(), filter.name); + if(name.empty()) + return; + filter.name = name; + filter.save(db); + } + } + } while (!done); +} + +void action_manage_channel_fitlers() { + + bool done = false; + + do { + std::vector filters = ChannelFilter::get_all(db); + std::vector names = {"Create new"}; + for(const ChannelFilter &filter: std::as_const(filters)) + names.push_back(filter.name); + const int selected = get_selection("Select channel filter", names); + if(selected < 0) { + done = true; + } else if(selected == 0) { + std::string name = get_string("Filter name", "Please enter a name for your filter:"); + if(!name.empty()) { + ChannelFilter::add(db, name); + } + } else if(selected-1 < (int)filters.size()) { + edit_channel_filter(filters.at(selected-1)); + } + } while(!done); +} + using json = nlohmann::json; std::optional load_json(const std::string &filename) { std::ifstream ifs(filename); @@ -746,6 +868,11 @@ static void run() add_channel_to_list(channel); } + for(const ChannelFilter &filter: ChannelFilter::get_all(db)) { + Channel ch = Channel::add_virtual(filter.name, filter); + add_channel_to_list(ch); + } + if(!channels.empty()) { select_channel_by_index(0); } @@ -778,6 +905,7 @@ static void run() {TERMPAINT_EV_KEY, "ArrowRight", 0, action_scroll_title_right, "Scroll title right"}, {TERMPAINT_EV_CHAR, "l", TERMPAINT_MOD_CTRL, [&](){ force_repaint = true; }, "Force redraw"}, {TERMPAINT_EV_KEY, "F2", 0, action_manage_user_flags, "Manage user flags"}, + {TERMPAINT_EV_KEY, "F3", 0, action_manage_channel_fitlers, "Manage channel filters"}, }; bool draw = true; diff --git a/db.cpp b/db.cpp index aa3b024..e57675a 100644 --- a/db.cpp +++ b/db.cpp @@ -91,6 +91,20 @@ CREATE TABLE user_flags ( ALTER TABLE videos ADD COLUMN added_to_playlist TEXT; UPDATE videos SET added_to_playlist = published, published = NULL; UPDATE settings SET value="2" WHERE key="schema_version"; +)"; + SC(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr)); + } + if(schema_version < 3) { + const std::string sql = R"( +CREATE TABLE channel_filters ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + video_mask INTEGER DEFAULT 0, + video_value INTEGER DEFAULT 0, + user_mask INTEGER DEFAULT 0, + user_value INTEGER DEFAULT 0 +); +UPDATE settings SET value="3" WHERE key="schema_version"; )"; SC(sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr)); } diff --git a/yt.cpp b/yt.cpp index 8fe9da7..5c0e97a 100644 --- a/yt.cpp +++ b/yt.cpp @@ -182,7 +182,7 @@ Channel Channel::add(sqlite3 *db, const std::string &selector, const std::string return Channel(channel_id, channel_name); } -Channel Channel::add_virtual(const std::string &name, const ChannelFilter filter) +Channel Channel::add_virtual(const std::string &name, const ChannelFilter &filter) { std::string id = name; std::transform(id.begin(), id.end(), id.begin(), [](char c){ return std::isalnum(c) ? std::tolower(c) : '-'; }); @@ -421,7 +421,57 @@ ChannelFilter::ChannelFilter(): id(-1), name(std::string()), video_mask(0), vide { } +ChannelFilter::ChannelFilter(sqlite3_stmt *row): id(get_int(row, 0)), name(get_string(row, 1)), + video_mask(get_int(row, 2)), video_value(get_int(row, 3)), user_mask(get_int(row, 4)), user_value(get_int(row, 5)) +{ +} + ChannelFilter::ChannelFilter(const int id, const std::string &name): id(id), name(name), video_mask(0), video_value(0), user_mask(0), user_value(0) { } + + +void ChannelFilter::save(sqlite3 *db) const +{ + if(id < 0) + return; + + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "UPDATE channel_filters SET name=?2, video_mask=?3, video_value=?4, user_mask=?5, user_value=?6 WHERE id = ?1;", -1, &query, nullptr)); + SC(sqlite3_bind_int(query, 1, id)); + SC(sqlite3_bind_text(query, 2, name.c_str(), -1, SQLITE_TRANSIENT)); + SC(sqlite3_bind_int(query, 3, video_mask)); + SC(sqlite3_bind_int(query, 4, video_value)); + SC(sqlite3_bind_int(query, 5, user_mask)); + SC(sqlite3_bind_int(query, 6, user_value)); + SC(sqlite3_step(query)); + SC(sqlite3_finalize(query)); +} + +ChannelFilter ChannelFilter::add(sqlite3 *db, const std::string &name) +{ + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "INSERT INTO channel_filters(name) values(?1);", -1, &query, nullptr)); + SC(sqlite3_bind_text(query, 1, name.c_str(), -1, nullptr)); + SC(sqlite3_step(query)); + SC(sqlite3_finalize(query)); + + int id = sqlite3_last_insert_rowid(db); + + return ChannelFilter(id, name); +} + +std::vector ChannelFilter::get_all(sqlite3 *db) +{ + std::vector result; + + sqlite3_stmt *query; + SC(sqlite3_prepare_v2(db, "SELECT * FROM channel_filters ORDER BY id;", -1, &query, nullptr)); + while(sqlite3_step(query) == SQLITE_ROW) { + result.emplace_back(query); + } + SC(sqlite3_finalize(query)); + + return result; +} diff --git a/yt.h b/yt.h index 79b9d20..ad97fa2 100644 --- a/yt.h +++ b/yt.h @@ -52,7 +52,11 @@ public: uint32_t user_value; ChannelFilter(); + ChannelFilter(sqlite3_stmt *row); + void save(sqlite3 *db) const; + static ChannelFilter add(sqlite3 *db, const std::string &name); + static std::vector get_all(sqlite3 *db); private: ChannelFilter(const int id, const std::string &name); }; @@ -69,7 +73,7 @@ public: Channel(sqlite3_stmt *row); static Channel add(sqlite3 *db, const std::string &selector, const std::string &value); - static Channel add_virtual(const std::string &name, const ChannelFilter filter); + static Channel add_virtual(const std::string &name, const ChannelFilter &filter); static std::vector get_all(sqlite3 *db); std::string upload_playlist() const;