From 804f051d6666897bbbe4df9976e7c97969cf8eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 20 Oct 2018 11:17:04 +0200 Subject: Implement sequential multiselect for Library tab --- nncmpp.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 12 deletions(-) diff --git a/nncmpp.c b/nncmpp.c index e9c4faf..85a6168 100644 --- a/nncmpp.c +++ b/nncmpp.c @@ -35,6 +35,10 @@ XX( ODD, "odd", -1, -1, 0 ) \ XX( DIRECTORY, "directory", -1, -1, 0 ) \ XX( SELECTION, "selection", -1, -1, A_REVERSE ) \ + /* Cyan is good with both black and white. + * Can't use A_REVERSE because bold'd be bright. + * Unfortunately ran out of B&W attributes. */ \ + XX( MULTISELECT,"multiselect",-1, 6, 0 ) \ XX( SCROLLBAR, "scrollbar", -1, -1, 0 ) \ /* These are for debugging only */ \ XX( WARNING, "warning", 3, -1, 0 ) \ @@ -569,6 +573,7 @@ struct tab int item_top; ///< Index of the topmost item int item_selected; ///< Index of the selected item + int item_mark; ///< Multiselect second point index }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -663,6 +668,7 @@ tab_init (struct tab *self, const char *name) // and we'd need to filter it first to replace invalid chars with '?' self->name_width = u8_strwidth ((uint8_t *) self->name, locale_charset ()); self->item_selected = 0; + self->item_mark = -1; } static void @@ -671,6 +677,17 @@ tab_free (struct tab *self) free (self->name); } +static struct tab_range { int from, upto; } +tab_selection_range (struct tab *self) +{ + if (self->item_selected < 0 || !self->item_count) + return (struct tab_range) { -1, -1 }; + if (self->item_mark < 0) + return (struct tab_range) { self->item_selected, self->item_selected }; + return (struct tab_range) { MIN (self->item_selected, self->item_mark), + MAX (self->item_selected, self->item_mark) }; +} + // --- Configuration ----------------------------------------------------------- static struct config_schema g_config_settings[] = @@ -1263,8 +1280,16 @@ app_draw_view (void) { int item_index = tab->item_top + row; int row_attrs = (item_index & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN); + + bool override_colors = true; if (item_index == tab->item_selected) row_attrs = APP_ATTR (SELECTION); + else if (tab->item_mark > -1 && + ((item_index >= tab->item_mark && item_index <= tab->item_selected) + || (item_index >= tab->item_selected && item_index <= tab->item_mark))) + row_attrs = APP_ATTR (MULTISELECT); + else + override_colors = false; struct row_buffer buf = row_buffer_make (); tab->on_item_draw (item_index, &buf, view_width); @@ -1274,7 +1299,7 @@ app_draw_view (void) for (size_t i = 0; i < buf.chars_len; i++) { chtype *attrs = &buf.chars[i].attrs; - if (item_index == tab->item_selected) + if (override_colors) *attrs = (*attrs & ~(A_COLOR | A_REVERSE)) | row_attrs; else if ((*attrs & A_COLOR) && (row_attrs & A_COLOR)) *attrs |= (row_attrs & ~A_COLOR); @@ -1323,7 +1348,15 @@ static void app_write_mpd_status (struct row_buffer *buf) { struct str_map *map = &g.playback_info; - if (str_map_find (map, "updating_db")) + if (g.active_tab->item_mark > -1) + { + struct tab_range r = tab_selection_range (g.active_tab); + char *msg = xstrdup_printf (r.from == r.upto + ? "Selected %d item" : "Selected %d items", r.upto - r.from + 1); + row_buffer_append (buf, msg, APP_ATTR (HIGHLIGHT)); + free (msg); + } + else if (str_map_find (map, "updating_db")) row_buffer_append (buf, "Updating database...", APP_ATTR (NORMAL)); else app_write_mpd_status_playlist (buf); @@ -1521,6 +1554,7 @@ app_goto_tab (int tab_index) XX( CHOOSE, "Choose item" ) \ XX( DELETE, "Delete item" ) \ XX( UP, "Go up a level" ) \ + XX( MULTISELECT, "Toggle multiselect" ) \ \ XX( SCROLL_UP, "Scroll up" ) \ XX( SCROLL_DOWN, "Scroll down" ) \ @@ -1670,13 +1704,25 @@ app_process_action (enum action action) // First let the tab try to handle this struct tab *tab = g.active_tab; if (tab->on_action && tab->on_action (action)) + { + tab->item_mark = -1; + app_invalidate (); return true; + } switch (action) { case ACTION_NONE: return true; case ACTION_QUIT: + // It is a pseudomode, avoid surprising the user + if (tab->item_mark > -1) + { + tab->item_mark = -1; + app_invalidate (); + return true; + } + app_quit (); return true; case ACTION_REDRAW: @@ -1691,6 +1737,18 @@ app_process_action (enum action action) default: return false; + case ACTION_MULTISELECT: + if (!tab->can_multiselect + || !tab->item_count || tab->item_selected < 0) + return false; + + app_invalidate (); + if (tab->item_mark > -1) + tab->item_mark = -1; + else + tab->item_mark = tab->item_selected; + return true; + case ACTION_LAST_TAB: if (!g.last_tab) return false; @@ -1943,6 +2001,7 @@ g_normal_defaults[] = { "Enter", ACTION_CHOOSE }, { "Delete", ACTION_DELETE }, { "Backspace", ACTION_UP }, + { "v", ACTION_MULTISELECT }, { "a", ACTION_MPD_ADD }, { "r", ACTION_MPD_REPLACE }, { ":", ACTION_MPD_COMMAND }, @@ -2187,6 +2246,7 @@ current_tab_init (void) { struct tab *super = &g_current_tab; tab_init (super, "Current"); + // TODO: implement multiselect, set can_multiselect to true super->on_action = current_tab_on_action; super->on_item_draw = current_tab_on_item_draw; return super; @@ -2412,6 +2472,9 @@ library_tab_on_data (const struct mpd_response *response, (int (*) (const void *, const void *)) library_tab_compare); g_library_tab.super.item_count = items->len; + // XXX: this unmarks even if just the database updates + g_library_tab.super.item_mark = -1; + // Don't force the selection visible when there's no need to touch it if (g_library_tab.super.item_selected >= (int) items->len) app_move_selection (0); @@ -2433,21 +2496,36 @@ library_tab_reload (const char *new_path) } static bool -library_tab_on_action (enum action action) +library_tab_is_range_playable (struct tab_range range) { - struct tab *self = g.active_tab; - if (self->item_selected < 0 || !self->item_count) - return false; + for (int i = range.from; i <= range.upto; i++) + { + struct library_tab_item x = + library_tab_resolve (g_library_tab.items.vector[i]); + if (x.type == LIBRARY_DIR || x.type == LIBRARY_FILE) + return true; + } + return false; +} +static bool +library_tab_on_action (enum action action) +{ struct mpd_client *c = &g.client; - if (c->state != MPD_CONNECTED) + struct tab_range range = tab_selection_range (&g_library_tab.super); + if (range.from < 0 || c->state != MPD_CONNECTED) return false; struct library_tab_item x = - library_tab_resolve (g_library_tab.items.vector[self->item_selected]); + library_tab_resolve (g_library_tab.items.vector[range.from]); + switch (action) { case ACTION_CHOOSE: + // I can't think of a reasonable way of handling that + if (range.from != range.upto) + break; + switch (x.type) { case LIBRARY_ROOT: @@ -2468,26 +2546,41 @@ library_tab_on_action (enum action action) return parent != NULL; } case ACTION_MPD_REPLACE: - if (x.type != LIBRARY_DIR && x.type != LIBRARY_FILE) + if (!library_tab_is_range_playable (range)) break; // Clears the playlist (which stops playback), add what user wanted // to replace it with, and eventually restore playback; // I can't think of a reliable alternative that omits the "play" mpd_client_list_begin (c); + mpd_client_send_command (c, "clear", NULL); - mpd_client_send_command (c, "add", x.path, NULL); + for (int i = range.from; i <= range.upto; i++) + { + struct library_tab_item x = + library_tab_resolve (g_library_tab.items.vector[i]); + if (x.type == LIBRARY_DIR || x.type == LIBRARY_FILE) + mpd_client_send_command (c, "add", x.path, NULL); + } if (g.state == PLAYER_PLAYING) mpd_client_send_command (c, "play", NULL); + mpd_client_list_end (c); mpd_client_add_task (c, mpd_on_simple_response, NULL); mpd_client_idle (c, 0); return true; case ACTION_MPD_ADD: - if (x.type != LIBRARY_DIR && x.type != LIBRARY_FILE) + if (!library_tab_is_range_playable (range)) break; - return MPD_SIMPLE ("add", x.path); + for (int i = range.from; i <= range.upto; i++) + { + struct library_tab_item x = + library_tab_resolve (g_library_tab.items.vector[i]); + if (x.type == LIBRARY_DIR || x.type == LIBRARY_FILE) + MPD_SIMPLE ("add", x.path); + } + return true; default: break; } @@ -2502,6 +2595,7 @@ library_tab_init (void) struct tab *super = &g_library_tab.super; tab_init (super, "Library"); + super->can_multiselect = true; super->on_action = library_tab_on_action; super->on_item_draw = library_tab_on_item_draw; return super; -- cgit v1.2.3