|
|
|
|
@@ -1366,7 +1366,6 @@ app_write_mpd_status (struct row_buffer *buf)
|
|
|
|
|
bool single = (s = str_map_find (map, "single")) && strcmp (s, "0");
|
|
|
|
|
bool consume = (s = str_map_find (map, "consume")) && strcmp (s, "0");
|
|
|
|
|
|
|
|
|
|
// TODO: remove the conditionals once we make them clickable
|
|
|
|
|
struct row_buffer right = row_buffer_make ();
|
|
|
|
|
chtype a[2] = { APP_ATTR (NORMAL), APP_ATTR (HIGHLIGHT) };
|
|
|
|
|
if (repeat) row_buffer_append_args (&right,
|
|
|
|
|
@@ -1545,8 +1544,13 @@ app_goto_tab (int tab_index)
|
|
|
|
|
XX( MPD_VOLUME_UP, "Increase volume" ) \
|
|
|
|
|
XX( MPD_VOLUME_DOWN, "Decrease volume" ) \
|
|
|
|
|
\
|
|
|
|
|
XX( MPD_SEARCH, "Global search" ) \
|
|
|
|
|
XX( MPD_ADD, "Add selection to playlist" ) \
|
|
|
|
|
XX( MPD_REPLACE, "Replace playlist" ) \
|
|
|
|
|
XX( MPD_REPEAT, "Toggle repeat" ) \
|
|
|
|
|
XX( MPD_RANDOM, "Toggle random playback" ) \
|
|
|
|
|
XX( MPD_SINGLE, "Toggle single song playback" ) \
|
|
|
|
|
XX( MPD_CONSUME, "Toggle consume" ) \
|
|
|
|
|
XX( MPD_UPDATE_DB, "Update MPD database" ) \
|
|
|
|
|
XX( MPD_COMMAND, "Send raw command to MPD" ) \
|
|
|
|
|
\
|
|
|
|
|
@@ -1697,6 +1701,14 @@ app_on_editor_end (bool confirmed)
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
app_mpd_toggle (const char *name)
|
|
|
|
|
{
|
|
|
|
|
const char *s = str_map_find (&g.playback_info, name);
|
|
|
|
|
bool value = s && strcmp (s, "0");
|
|
|
|
|
return MPD_SIMPLE (name, value ? "0" : "1");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
app_process_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
@@ -1704,7 +1716,6 @@ app_process_action (enum action action)
|
|
|
|
|
struct tab *tab = g.active_tab;
|
|
|
|
|
if (tab->on_action && tab->on_action (action))
|
|
|
|
|
{
|
|
|
|
|
tab->item_mark = -1;
|
|
|
|
|
app_invalidate ();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
@@ -1766,6 +1777,10 @@ app_process_action (enum action action)
|
|
|
|
|
case ACTION_MPD_NEXT: return MPD_SIMPLE ("next");
|
|
|
|
|
case ACTION_MPD_FORWARD: return MPD_SIMPLE ("seekcur", "+10");
|
|
|
|
|
case ACTION_MPD_BACKWARD: return MPD_SIMPLE ("seekcur", "-10");
|
|
|
|
|
case ACTION_MPD_REPEAT: return app_mpd_toggle ("repeat");
|
|
|
|
|
case ACTION_MPD_RANDOM: return app_mpd_toggle ("random");
|
|
|
|
|
case ACTION_MPD_SINGLE: return app_mpd_toggle ("single");
|
|
|
|
|
case ACTION_MPD_CONSUME: return app_mpd_toggle ("consume");
|
|
|
|
|
case ACTION_MPD_UPDATE_DB: return MPD_SIMPLE ("update");
|
|
|
|
|
|
|
|
|
|
case ACTION_MPD_VOLUME_UP: return app_setvol (g.volume + 10);
|
|
|
|
|
@@ -1979,6 +1994,8 @@ g_normal_defaults[] =
|
|
|
|
|
{ "End", ACTION_GOTO_BOTTOM },
|
|
|
|
|
{ "M-<", ACTION_GOTO_TOP },
|
|
|
|
|
{ "M->", ACTION_GOTO_BOTTOM },
|
|
|
|
|
{ "g", ACTION_GOTO_TOP },
|
|
|
|
|
{ "G", ACTION_GOTO_BOTTOM },
|
|
|
|
|
{ "S-Up", ACTION_MOVE_UP },
|
|
|
|
|
{ "S-Down", ACTION_MOVE_DOWN },
|
|
|
|
|
{ "Up", ACTION_GOTO_ITEM_PREVIOUS },
|
|
|
|
|
@@ -1999,8 +2016,10 @@ g_normal_defaults[] =
|
|
|
|
|
// Not sure how to set these up, they're pretty arbitrary so far
|
|
|
|
|
{ "Enter", ACTION_CHOOSE },
|
|
|
|
|
{ "Delete", ACTION_DELETE },
|
|
|
|
|
{ "d", ACTION_DELETE },
|
|
|
|
|
{ "Backspace", ACTION_UP },
|
|
|
|
|
{ "v", ACTION_MULTISELECT },
|
|
|
|
|
{ "/", ACTION_MPD_SEARCH },
|
|
|
|
|
{ "a", ACTION_MPD_ADD },
|
|
|
|
|
{ "r", ACTION_MPD_REPLACE },
|
|
|
|
|
{ ":", ACTION_MPD_COMMAND },
|
|
|
|
|
@@ -2188,55 +2207,108 @@ current_tab_on_item_draw (size_t item_index, struct row_buffer *buffer,
|
|
|
|
|
free (s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
current_tab_move_song (const char *id, int diff)
|
|
|
|
|
static void
|
|
|
|
|
mpd_on_move_response (const struct mpd_response *response,
|
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
(void) data;
|
|
|
|
|
|
|
|
|
|
*(bool *) user_data = false;
|
|
|
|
|
if (!response->success)
|
|
|
|
|
print_error ("%s: %s", "command failed", response->message_text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
current_tab_move (int from, int to)
|
|
|
|
|
{
|
|
|
|
|
compact_map_t map;
|
|
|
|
|
const char *id;
|
|
|
|
|
if (!(map = item_list_get (&g.playlist, from))
|
|
|
|
|
|| !(id = compact_map_find (map, "id")))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
char *target_str = xstrdup_printf ("%d", to);
|
|
|
|
|
mpd_client_send_command (&g.client, "moveid", id, target_str, NULL);
|
|
|
|
|
free (target_str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
current_tab_move_selection (int diff)
|
|
|
|
|
{
|
|
|
|
|
static bool already_moving;
|
|
|
|
|
if (already_moving || diff == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
|
int target = g_current_tab.item_selected + diff;
|
|
|
|
|
if (c->state != MPD_CONNECTED || target < 0)
|
|
|
|
|
if (c->state != MPD_CONNECTED)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
char *target_str = xstrdup_printf ("%d", target);
|
|
|
|
|
mpd_client_send_command (c, "moveid", id, target_str, NULL);
|
|
|
|
|
free (target_str);
|
|
|
|
|
// TODO: we should create a cancellable action waiting for the move to
|
|
|
|
|
// finish, so that holding Shift-arrows works as expected.
|
|
|
|
|
mpd_client_add_task (c, mpd_on_simple_response, NULL);
|
|
|
|
|
mpd_client_idle (c, 0);
|
|
|
|
|
struct tab *tab = &g_current_tab;
|
|
|
|
|
struct tab_range range = tab_selection_range (tab);
|
|
|
|
|
if (range.from + diff < 0
|
|
|
|
|
|| range.upto + diff >= (int) tab->item_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// XXX: this behaves a bit erratically, as even if we waited for
|
|
|
|
|
// a confirmation from the daemon, it would precede the playlist update
|
|
|
|
|
g_current_tab.item_selected = target;
|
|
|
|
|
app_move_selection (0);
|
|
|
|
|
return true;
|
|
|
|
|
mpd_client_list_begin (c);
|
|
|
|
|
if (diff < 0)
|
|
|
|
|
for (int i = range.from; i <= range.upto; i++)
|
|
|
|
|
current_tab_move (i, i + diff);
|
|
|
|
|
else
|
|
|
|
|
for (int i = range.upto; i >= range.from; i--)
|
|
|
|
|
current_tab_move (i, i + diff);
|
|
|
|
|
mpd_client_list_end (c);
|
|
|
|
|
|
|
|
|
|
mpd_client_add_task (c, mpd_on_move_response, &already_moving);
|
|
|
|
|
mpd_client_idle (c, 0);
|
|
|
|
|
return already_moving = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
current_tab_on_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
struct tab *self = g.active_tab;
|
|
|
|
|
compact_map_t map = item_list_get (&g.playlist, self->item_selected);
|
|
|
|
|
|
|
|
|
|
const char *id;
|
|
|
|
|
if (!map || !(id = compact_map_find (map, "id")))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct tab *tab = &g_current_tab;
|
|
|
|
|
compact_map_t map = item_list_get (&g.playlist, tab->item_selected);
|
|
|
|
|
switch (action)
|
|
|
|
|
{
|
|
|
|
|
case ACTION_MOVE_UP: return current_tab_move_song (id, -1);
|
|
|
|
|
case ACTION_MOVE_DOWN: return current_tab_move_song (id, 1);
|
|
|
|
|
case ACTION_CHOOSE: return MPD_SIMPLE ("playid", id);
|
|
|
|
|
case ACTION_DELETE: return MPD_SIMPLE ("deleteid", id);
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const char *id;
|
|
|
|
|
case ACTION_MOVE_UP:
|
|
|
|
|
return current_tab_move_selection (-1);
|
|
|
|
|
case ACTION_MOVE_DOWN:
|
|
|
|
|
return current_tab_move_selection (+1);
|
|
|
|
|
case ACTION_CHOOSE:
|
|
|
|
|
tab->item_mark = -1;
|
|
|
|
|
return map && (id = compact_map_find (map, "id"))
|
|
|
|
|
&& MPD_SIMPLE ("playid", id);
|
|
|
|
|
case ACTION_DELETE:
|
|
|
|
|
{
|
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
|
struct tab_range range = tab_selection_range (tab);
|
|
|
|
|
if (range.from < 0 || c->state != MPD_CONNECTED)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
mpd_client_list_begin (c);
|
|
|
|
|
for (int i = range.from; i <= range.upto; i++)
|
|
|
|
|
{
|
|
|
|
|
if ((map = item_list_get (&g.playlist, i))
|
|
|
|
|
&& (id = compact_map_find (map, "id")))
|
|
|
|
|
mpd_client_send_command (c, "deleteid", id, NULL);
|
|
|
|
|
}
|
|
|
|
|
mpd_client_list_end (c);
|
|
|
|
|
mpd_client_add_task (c, mpd_on_simple_response, NULL);
|
|
|
|
|
mpd_client_idle (c, 0);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
current_tab_update (void)
|
|
|
|
|
{
|
|
|
|
|
g_current_tab.item_count = g.playlist.len;
|
|
|
|
|
g_current_tab.item_mark =
|
|
|
|
|
MIN ((int) g.playlist.len - 1, g_current_tab.item_mark);
|
|
|
|
|
app_invalidate ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -2245,7 +2317,7 @@ current_tab_init (void)
|
|
|
|
|
{
|
|
|
|
|
struct tab *super = &g_current_tab;
|
|
|
|
|
tab_init (super, "Current");
|
|
|
|
|
// TODO: implement multiselect, set can_multiselect to true
|
|
|
|
|
super->can_multiselect = true;
|
|
|
|
|
super->on_action = current_tab_on_action;
|
|
|
|
|
super->on_item_draw = current_tab_on_item_draw;
|
|
|
|
|
return super;
|
|
|
|
|
@@ -2268,6 +2340,8 @@ static struct
|
|
|
|
|
struct str path; ///< Current path
|
|
|
|
|
struct strv items; ///< Current items (type, name, path)
|
|
|
|
|
struct library_level *above; ///< Upper levels
|
|
|
|
|
|
|
|
|
|
bool searching; ///< Search mode is active
|
|
|
|
|
}
|
|
|
|
|
g_library_tab;
|
|
|
|
|
|
|
|
|
|
@@ -2375,13 +2449,13 @@ library_tab_parent (void)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
library_tab_is_above (const char *above, const char *path)
|
|
|
|
|
library_tab_is_above (const char *above, const char *subdir)
|
|
|
|
|
{
|
|
|
|
|
size_t above_len = strlen (above);
|
|
|
|
|
if (strncmp (above, path, above_len))
|
|
|
|
|
if (strncmp (above, subdir, above_len))
|
|
|
|
|
return false;
|
|
|
|
|
// The root is an empty string and is above anything other than itself
|
|
|
|
|
return path[above_len] == '/' || (*path && !*above);
|
|
|
|
|
return subdir[above_len] == '/' || (*subdir && !*above);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
@@ -2421,26 +2495,16 @@ library_tab_change_level (const char *new_path)
|
|
|
|
|
|
|
|
|
|
free (g_library_tab.super.header);
|
|
|
|
|
g_library_tab.super.header = NULL;
|
|
|
|
|
g_library_tab.super.item_mark = -1;
|
|
|
|
|
|
|
|
|
|
if (path->len)
|
|
|
|
|
g_library_tab.super.header = xstrdup_printf ("/%s", path->str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
library_tab_on_data (const struct mpd_response *response,
|
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
|
library_tab_load_data (const struct strv *data)
|
|
|
|
|
{
|
|
|
|
|
char *new_path = user_data;
|
|
|
|
|
if (!response->success)
|
|
|
|
|
{
|
|
|
|
|
print_error ("cannot read directory: %s", response->message_text);
|
|
|
|
|
free (new_path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
strv_reset (&g_library_tab.items);
|
|
|
|
|
library_tab_change_level (new_path);
|
|
|
|
|
free (new_path);
|
|
|
|
|
|
|
|
|
|
char *parent = library_tab_parent ();
|
|
|
|
|
if (parent)
|
|
|
|
|
@@ -2481,9 +2545,31 @@ library_tab_on_data (const struct mpd_response *response,
|
|
|
|
|
app_invalidate ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
library_tab_on_data (const struct mpd_response *response,
|
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
char *new_path = user_data;
|
|
|
|
|
if (!response->success)
|
|
|
|
|
{
|
|
|
|
|
print_error ("cannot read directory: %s", response->message_text);
|
|
|
|
|
free (new_path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_library_tab.searching = false;
|
|
|
|
|
library_tab_change_level (new_path);
|
|
|
|
|
free (new_path);
|
|
|
|
|
|
|
|
|
|
library_tab_load_data (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
library_tab_reload (const char *new_path)
|
|
|
|
|
{
|
|
|
|
|
if (!new_path && g_library_tab.searching)
|
|
|
|
|
return; // TODO: perhaps we should call search_on_changed()
|
|
|
|
|
|
|
|
|
|
char *path = new_path
|
|
|
|
|
? xstrdup (new_path)
|
|
|
|
|
: xstrdup (g_library_tab.path.str);
|
|
|
|
|
@@ -2494,6 +2580,48 @@ library_tab_reload (const char *new_path)
|
|
|
|
|
mpd_client_idle (c, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
library_tab_on_search_data (const struct mpd_response *response,
|
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
(void) user_data;
|
|
|
|
|
if (!g_library_tab.searching)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!response->success)
|
|
|
|
|
{
|
|
|
|
|
print_error ("cannot search: %s", response->message_text);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
library_tab_load_data (data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
search_on_changed (void)
|
|
|
|
|
{
|
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
|
|
|
|
|
|
size_t len;
|
|
|
|
|
char *u8 = (char *) u32_to_u8 (g.editor.line, g.editor.len + 1, NULL, &len);
|
|
|
|
|
mpd_client_send_command (c, "search", "any", u8, NULL);
|
|
|
|
|
free (u8);
|
|
|
|
|
|
|
|
|
|
mpd_client_add_task (c, library_tab_on_search_data, NULL);
|
|
|
|
|
mpd_client_idle (c, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
search_on_end (bool confirmed)
|
|
|
|
|
{
|
|
|
|
|
if (!confirmed)
|
|
|
|
|
library_tab_reload (g_library_tab.above->path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
library_tab_is_range_playable (struct tab_range range)
|
|
|
|
|
{
|
|
|
|
|
@@ -2511,8 +2639,12 @@ static bool
|
|
|
|
|
library_tab_on_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
struct mpd_client *c = &g.client;
|
|
|
|
|
struct tab_range range = tab_selection_range (&g_library_tab.super);
|
|
|
|
|
if (range.from < 0 || c->state != MPD_CONNECTED)
|
|
|
|
|
if (c->state != MPD_CONNECTED)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct tab *tab = &g_library_tab.super;
|
|
|
|
|
struct tab_range range = tab_selection_range (tab);
|
|
|
|
|
if (range.from < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct library_tab_item x =
|
|
|
|
|
@@ -2533,6 +2665,7 @@ library_tab_on_action (enum action action)
|
|
|
|
|
case LIBRARY_FILE: MPD_SIMPLE ("add", x.path); break;
|
|
|
|
|
default: hard_assert (!"invalid item type");
|
|
|
|
|
}
|
|
|
|
|
tab->item_mark = -1;
|
|
|
|
|
return true;
|
|
|
|
|
case ACTION_UP:
|
|
|
|
|
{
|
|
|
|
|
@@ -2544,6 +2677,48 @@ library_tab_on_action (enum action action)
|
|
|
|
|
}
|
|
|
|
|
return parent != NULL;
|
|
|
|
|
}
|
|
|
|
|
case ACTION_MPD_SEARCH:
|
|
|
|
|
{
|
|
|
|
|
line_editor_start (&g.editor, '/');
|
|
|
|
|
g.editor.on_changed = search_on_changed;
|
|
|
|
|
g.editor.on_end = search_on_end;
|
|
|
|
|
|
|
|
|
|
// We just need to be deeper but not match anything real,
|
|
|
|
|
// in order to keep the rest of the codebase functional as-is
|
|
|
|
|
if (!g_library_tab.searching)
|
|
|
|
|
{
|
|
|
|
|
char *fake_subdir = xstrdup_printf ("%s/", g_library_tab.path.str);
|
|
|
|
|
library_tab_change_level (fake_subdir);
|
|
|
|
|
free (fake_subdir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free (tab->header);
|
|
|
|
|
tab->header = xstrdup_printf ("Global search");
|
|
|
|
|
g_library_tab.searching = true;
|
|
|
|
|
|
|
|
|
|
// Since we've already changed the header, empty the list,
|
|
|
|
|
// although to be consistent we should also ask to search for "",
|
|
|
|
|
// which dumps the database
|
|
|
|
|
struct strv empty = strv_make ();
|
|
|
|
|
library_tab_load_data (&empty);
|
|
|
|
|
strv_free (&empty);
|
|
|
|
|
|
|
|
|
|
app_invalidate ();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
case ACTION_MPD_ADD:
|
|
|
|
|
if (!library_tab_is_range_playable (range))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
tab->item_mark = -1;
|
|
|
|
|
return true;
|
|
|
|
|
case ACTION_MPD_REPLACE:
|
|
|
|
|
if (!library_tab_is_range_playable (range))
|
|
|
|
|
break;
|
|
|
|
|
@@ -2567,18 +2742,7 @@ library_tab_on_action (enum action action)
|
|
|
|
|
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 (!library_tab_is_range_playable (range))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
tab->item_mark = -1;
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
@@ -2821,12 +2985,12 @@ error:
|
|
|
|
|
static bool
|
|
|
|
|
streams_tab_on_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
struct tab *self = g.active_tab;
|
|
|
|
|
if (self->item_selected < 0 || !self->item_count)
|
|
|
|
|
struct tab *tab = g.active_tab;
|
|
|
|
|
if (tab->item_selected < 0 || !tab->item_count)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// For simplicity the URL is the string following the stream name
|
|
|
|
|
const char *uri = 1 + strchr (g.streams.vector[self->item_selected], 0);
|
|
|
|
|
const char *uri = 1 + strchr (g.streams.vector[tab->item_selected], 0);
|
|
|
|
|
|
|
|
|
|
struct error *e = NULL;
|
|
|
|
|
switch (action)
|
|
|
|
|
@@ -2942,7 +3106,28 @@ info_tab_init (void)
|
|
|
|
|
|
|
|
|
|
// --- Help tab ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static struct strv g_help_tab_lines;
|
|
|
|
|
static struct
|
|
|
|
|
{
|
|
|
|
|
struct tab super; ///< Parent class
|
|
|
|
|
ARRAY (enum action, actions) ///< Actions for content
|
|
|
|
|
struct strv lines; ///< Visible content
|
|
|
|
|
}
|
|
|
|
|
g_help_tab;
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
help_tab_on_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
struct tab *tab = &g_help_tab.super;
|
|
|
|
|
if (tab->item_selected < 0
|
|
|
|
|
|| tab->item_selected >= (int) g_help_tab.actions_len
|
|
|
|
|
|| action != ACTION_CHOOSE)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
action = g_help_tab.actions[tab->item_selected];
|
|
|
|
|
return action != ACTION_NONE
|
|
|
|
|
&& action != ACTION_CHOOSE // avoid recursion
|
|
|
|
|
&& app_process_action (action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
help_tab_strfkey (const termo_key_t *key, struct strv *out)
|
|
|
|
|
@@ -2960,7 +3145,20 @@ help_tab_strfkey (const termo_key_t *key, struct strv *out)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
help_tab_group (struct binding *keys, size_t len, struct strv *out)
|
|
|
|
|
help_tab_assign_action (enum action action)
|
|
|
|
|
{
|
|
|
|
|
hard_assert (g_help_tab.lines.len > g_help_tab.actions_len);
|
|
|
|
|
|
|
|
|
|
size_t to_push = g_help_tab.lines.len - g_help_tab.actions_len;
|
|
|
|
|
ARRAY_RESERVE (g_help_tab.actions, to_push);
|
|
|
|
|
for (size_t i = 1; i < to_push; i++)
|
|
|
|
|
g_help_tab.actions[g_help_tab.actions_len++] = ACTION_NONE;
|
|
|
|
|
g_help_tab.actions[g_help_tab.actions_len++] = action;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
help_tab_group (struct binding *keys, size_t len, struct strv *out,
|
|
|
|
|
bool bound[ACTION_COUNT])
|
|
|
|
|
{
|
|
|
|
|
for (enum action i = 0; i < ACTION_COUNT; i++)
|
|
|
|
|
{
|
|
|
|
|
@@ -2974,39 +3172,71 @@ help_tab_group (struct binding *keys, size_t len, struct strv *out)
|
|
|
|
|
strv_append_owned (out, xstrdup_printf
|
|
|
|
|
(" %-30s %s", g_actions[i].description, joined));
|
|
|
|
|
free (joined);
|
|
|
|
|
|
|
|
|
|
bound[i] = true;
|
|
|
|
|
help_tab_assign_action (i);
|
|
|
|
|
}
|
|
|
|
|
strv_free (&ass);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
help_tab_unbound (struct strv *out, bool bound[ACTION_COUNT])
|
|
|
|
|
{
|
|
|
|
|
for (enum action i = 0; i < ACTION_COUNT; i++)
|
|
|
|
|
if (!bound[i])
|
|
|
|
|
{
|
|
|
|
|
strv_append_owned (out,
|
|
|
|
|
xstrdup_printf (" %-30s", g_actions[i].description));
|
|
|
|
|
help_tab_assign_action (i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
help_tab_on_item_draw (size_t item_index, struct row_buffer *buffer, int width)
|
|
|
|
|
{
|
|
|
|
|
(void) width;
|
|
|
|
|
|
|
|
|
|
hard_assert (item_index < g_help_tab_lines.len);
|
|
|
|
|
const char *line = g_help_tab_lines.vector[item_index];
|
|
|
|
|
hard_assert (item_index < g_help_tab.lines.len);
|
|
|
|
|
const char *line = g_help_tab.lines.vector[item_index];
|
|
|
|
|
row_buffer_append (buffer, line, *line == ' ' ? 0 : A_BOLD);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct tab *
|
|
|
|
|
help_tab_init (void)
|
|
|
|
|
{
|
|
|
|
|
g_help_tab_lines = strv_make ();
|
|
|
|
|
ARRAY_INIT (g_help_tab.actions);
|
|
|
|
|
struct strv *lines = &g_help_tab.lines;
|
|
|
|
|
*lines = strv_make ();
|
|
|
|
|
|
|
|
|
|
strv_append (&g_help_tab_lines, "Normal mode actions");
|
|
|
|
|
help_tab_group (g_normal_keys, g_normal_keys_len, &g_help_tab_lines);
|
|
|
|
|
strv_append (&g_help_tab_lines, "");
|
|
|
|
|
bool bound[ACTION_COUNT] = { [ACTION_NONE] = true };
|
|
|
|
|
|
|
|
|
|
strv_append (&g_help_tab_lines, "Editor mode actions");
|
|
|
|
|
help_tab_group (g_editor_keys, g_editor_keys_len, &g_help_tab_lines);
|
|
|
|
|
strv_append (&g_help_tab_lines, "");
|
|
|
|
|
strv_append (lines, "Normal mode actions");
|
|
|
|
|
help_tab_group (g_normal_keys, g_normal_keys_len, lines, bound);
|
|
|
|
|
strv_append (lines, "");
|
|
|
|
|
|
|
|
|
|
static struct tab super;
|
|
|
|
|
tab_init (&super, "Help");
|
|
|
|
|
super.on_item_draw = help_tab_on_item_draw;
|
|
|
|
|
super.item_count = g_help_tab_lines.len;
|
|
|
|
|
return &super;
|
|
|
|
|
strv_append (lines, "Editor mode actions");
|
|
|
|
|
help_tab_group (g_editor_keys, g_editor_keys_len, lines, bound);
|
|
|
|
|
strv_append (lines, "");
|
|
|
|
|
|
|
|
|
|
bool have_unbound = false;
|
|
|
|
|
for (enum action i = 0; i < ACTION_COUNT; i++)
|
|
|
|
|
if (!bound[i])
|
|
|
|
|
have_unbound = true;
|
|
|
|
|
|
|
|
|
|
if (have_unbound)
|
|
|
|
|
{
|
|
|
|
|
strv_append (lines, "Unbound actions");
|
|
|
|
|
help_tab_unbound (lines, bound);
|
|
|
|
|
strv_append (lines, "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct tab *super = &g_help_tab.super;
|
|
|
|
|
tab_init (super, "Help");
|
|
|
|
|
super->on_action = help_tab_on_action;
|
|
|
|
|
super->on_item_draw = help_tab_on_item_draw;
|
|
|
|
|
super->item_count = lines->len;
|
|
|
|
|
return super;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Debug tab ---------------------------------------------------------------
|
|
|
|
|
@@ -3171,7 +3401,7 @@ mpd_update_playback_state (void)
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mpd_process_info (const struct strv *data)
|
|
|
|
|
mpd_process_info_data (const struct strv *data)
|
|
|
|
|
{
|
|
|
|
|
struct str_map *map = &g.playback_info;
|
|
|
|
|
|
|
|
|
|
@@ -3205,6 +3435,57 @@ mpd_process_info (const struct strv *data)
|
|
|
|
|
str_map_free (&item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find a song by its id in the current playlist. Expensive, rarely called.
|
|
|
|
|
static ssize_t
|
|
|
|
|
mpd_find_pos_of_id (const char *desired_id)
|
|
|
|
|
{
|
|
|
|
|
compact_map_t map;
|
|
|
|
|
const char *id;
|
|
|
|
|
for (size_t i = 0; i < g.playlist.len; i++)
|
|
|
|
|
{
|
|
|
|
|
if ((map = item_list_get (&g.playlist, i))
|
|
|
|
|
&& (id = compact_map_find (map, "id"))
|
|
|
|
|
&& !strcmp (id, desired_id))
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
mpd_id_of_pos (int pos)
|
|
|
|
|
{
|
|
|
|
|
compact_map_t map = item_list_get (&g.playlist, pos);
|
|
|
|
|
return map ? compact_map_find (map, "id") : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mpd_process_info (const struct strv *data)
|
|
|
|
|
{
|
|
|
|
|
int *selected = &g_current_tab.item_selected;
|
|
|
|
|
int *marked = &g_current_tab.item_mark;
|
|
|
|
|
char *prev_sel_id = mpd_id_of_pos (*selected);
|
|
|
|
|
char *prev_mark_id = mpd_id_of_pos (*marked);
|
|
|
|
|
if (prev_sel_id) prev_sel_id = xstrdup (prev_sel_id);
|
|
|
|
|
if (prev_mark_id) prev_mark_id = xstrdup (prev_mark_id);
|
|
|
|
|
|
|
|
|
|
mpd_process_info_data (data);
|
|
|
|
|
|
|
|
|
|
const char *sel_id = mpd_id_of_pos (*selected);
|
|
|
|
|
const char *mark_id = mpd_id_of_pos (*marked);
|
|
|
|
|
|
|
|
|
|
if (prev_mark_id && (!mark_id || strcmp (prev_mark_id, mark_id)))
|
|
|
|
|
*marked = mpd_find_pos_of_id (prev_mark_id);
|
|
|
|
|
if (prev_sel_id && (!sel_id || strcmp (prev_sel_id, sel_id)))
|
|
|
|
|
{
|
|
|
|
|
if ((*selected = mpd_find_pos_of_id (prev_sel_id)) < 0)
|
|
|
|
|
*marked = -1;
|
|
|
|
|
app_move_selection (0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free (prev_sel_id);
|
|
|
|
|
free (prev_mark_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mpd_on_info_response (const struct mpd_response *response,
|
|
|
|
|
const struct strv *data, void *user_data)
|
|
|
|
|
|