Implement sequential multiselect for Library tab
This commit is contained in:
		
							parent
							
								
									3c09a16a02
								
							
						
					
					
						commit
						804f051d66
					
				
							
								
								
									
										118
									
								
								nncmpp.c
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								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); | ||||
| @ -2432,22 +2495,37 @@ library_tab_reload (const char *new_path) | ||||
| 	mpd_client_idle (c, 0); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| library_tab_is_range_playable (struct tab_range range) | ||||
| { | ||||
| 	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 tab *self = g.active_tab; | ||||
| 	if (self->item_selected < 0 || !self->item_count) | ||||
| 		return false; | ||||
| 
 | ||||
| 	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; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user