Add edit support for channel filters

This commit is contained in:
Daniel Schulte 2021-07-22 00:03:20 +02:00
parent 81aaff70ac
commit 1216d5840f
4 changed files with 206 additions and 10 deletions

View File

@ -533,6 +533,7 @@ void action_add_new_user_flag() {
void action_rename_user_flag() {
std::vector<std::string> 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<action> 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; i<userFlags.size(); i++) {
const int row = 3 + i / 2;
const int col = i % 2;
const UserFlag &f = userFlags.at(i);
if(i < userFlags.size()) {
draw_flag(divider_pos*col + flag_pos, row, userflag_keys_str.at(i), f.name, filter.user_mask & f.id, filter.user_value & f.id);
}
}
tp_flush(false);
auto event = tp_wait_for_event();
if(!event)
abort();
if(!tui_handle_action(*event, actions)) {
if(event->type == 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<<index); });
if(flag == userFlags.cend())
continue;
toggle_flag(filter.user_mask, filter.user_value, flag->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<ChannelFilter> filters = ChannelFilter::get_all(db);
std::vector<std::string> 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<json> 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;

14
db.cpp
View File

@ -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));
}

52
yt.cpp
View File

@ -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> ChannelFilter::get_all(sqlite3 *db)
{
std::vector<ChannelFilter> 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;
}

6
yt.h
View File

@ -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<ChannelFilter> 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<Channel> get_all(sqlite3 *db);
std::string upload_playlist() const;