screen_queue: move playlist_save() to save_playlist.c
[ncmpc.git] / src / screen_queue.c
blob3d20da3bc999ad10f62e13dfb3a9aa9c7e0471f5
1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "screen_queue.h"
21 #include "screen_interface.h"
22 #include "screen_file.h"
23 #include "screen_status.h"
24 #include "screen_find.h"
25 #include "save_playlist.h"
26 #include "config.h"
27 #include "i18n.h"
28 #include "charset.h"
29 #include "options.h"
30 #include "mpdclient.h"
31 #include "utils.h"
32 #include "strfsong.h"
33 #include "wreadln.h"
34 #include "song_paint.h"
35 #include "screen.h"
36 #include "screen_utils.h"
37 #include "screen_song.h"
38 #include "screen_lyrics.h"
39 #include "Compiler.h"
41 #ifndef NCMPC_MINI
42 #include "hscroll.h"
43 #endif
45 #include <mpd/client.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <glib.h>
51 #define MAX_SONG_LENGTH 512
53 #ifndef NCMPC_MINI
54 typedef struct
56 GList **list;
57 GList **dir_list;
58 struct mpdclient *c;
59 } completion_callback_data_t;
61 static struct hscroll hscroll;
62 #endif
64 static struct mpdclient_playlist *playlist;
65 static int current_song_id = -1;
66 static int selected_song_id = -1;
67 static struct list_window *lw;
68 static guint timer_hide_cursor_id;
70 static void
71 screen_queue_paint(void);
73 static void
74 screen_queue_repaint(void)
76 screen_queue_paint();
77 wrefresh(lw->w);
80 static const struct mpd_song *
81 screen_queue_selected_song(void)
83 return !lw->range_selection &&
84 lw->selected < playlist_length(playlist)
85 ? playlist_get(playlist, lw->selected)
86 : NULL;
89 static void
90 screen_queue_save_selection(void)
92 selected_song_id = screen_queue_selected_song() != NULL
93 ? (int)mpd_song_get_id(screen_queue_selected_song())
94 : -1;
97 static void
98 screen_queue_restore_selection(void)
100 list_window_set_length(lw, playlist_length(playlist));
102 if (selected_song_id < 0)
103 /* there was no selection */
104 return;
106 const struct mpd_song *song = screen_queue_selected_song();
107 if (song != NULL &&
108 mpd_song_get_id(song) == (unsigned)selected_song_id)
109 /* selection is still valid */
110 return;
112 int pos = playlist_get_index_from_id(playlist, selected_song_id);
113 if (pos >= 0)
114 list_window_set_cursor(lw, pos);
116 screen_queue_save_selection();
119 static const char *
120 screen_queue_lw_callback(unsigned idx, gcc_unused void *data)
122 static char songname[MAX_SONG_LENGTH];
124 assert(playlist != NULL);
125 assert(idx < playlist_length(playlist));
127 struct mpd_song *song = playlist_get(playlist, idx);
129 strfsong(songname, MAX_SONG_LENGTH, options.list_format, song);
131 return songname;
134 static void
135 center_playing_item(const struct mpd_status *status, bool center_cursor)
137 if (status == NULL ||
138 (mpd_status_get_state(status) != MPD_STATE_PLAY &&
139 mpd_status_get_state(status) != MPD_STATE_PAUSE))
140 return;
142 /* try to center the song that are playing */
143 int idx = mpd_status_get_song_pos(status);
144 if (idx < 0)
145 return;
147 list_window_center(lw, idx);
149 if (center_cursor) {
150 list_window_set_cursor(lw, idx);
151 return;
154 /* make sure the cursor is in the window */
155 list_window_fetch_cursor(lw);
158 gcc_pure
159 static int
160 get_current_song_id(const struct mpd_status *status)
162 return status != NULL &&
163 (mpd_status_get_state(status) == MPD_STATE_PLAY ||
164 mpd_status_get_state(status) == MPD_STATE_PAUSE)
165 ? (int)mpd_status_get_song_id(status)
166 : -1;
169 static bool
170 screen_queue_song_change(const struct mpd_status *status)
172 if (get_current_song_id(status) == current_song_id)
173 return false;
175 current_song_id = get_current_song_id(status);
177 /* center the cursor */
178 if (options.auto_center && !lw->range_selection)
179 center_playing_item(status, false);
181 return true;
184 #ifndef NCMPC_MINI
186 * Wrapper for strncmp(). We are not allowed to pass &strncmp to
187 * g_completion_set_compare(), because strncmp() takes size_t where
188 * g_completion_set_compare passes a gsize value.
190 static gint
191 completion_strncmp(const gchar *s1, const gchar *s2, gsize n)
193 return strncmp(s1, s2, n);
195 #endif
197 #ifndef NCMPC_MINI
198 static void add_dir(GCompletion *gcmp, gchar *dir, GList **dir_list,
199 GList **list, struct mpdclient *c)
201 g_completion_remove_items(gcmp, *list);
202 *list = string_list_remove(*list, dir);
203 *list = gcmp_list_from_path(c, dir, *list, GCMP_TYPE_RFILE);
204 g_completion_add_items(gcmp, *list);
205 *dir_list = g_list_append(*dir_list, g_strdup(dir));
208 static void add_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
210 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
211 GList **dir_list = tmp->dir_list;
212 GList **list = tmp->list;
213 struct mpdclient *c = tmp->c;
215 if (*list == NULL) {
216 /* create initial list */
217 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_RFILE);
218 g_completion_add_items(gcmp, *list);
219 } else if (line && line[0] && line[strlen(line)-1]=='/' &&
220 string_list_find(*dir_list, line) == NULL) {
221 /* add directory content to list */
222 add_dir(gcmp, line, dir_list, list, c);
226 static void add_post_completion_cb(GCompletion *gcmp, gchar *line,
227 GList *items, void *data)
229 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
230 GList **dir_list = tmp->dir_list;
231 GList **list = tmp->list;
232 struct mpdclient *c = tmp->c;
234 if (g_list_length(items) >= 1)
235 screen_display_completion_list(items);
237 if (line && line[0] && line[strlen(line) - 1] == '/' &&
238 string_list_find(*dir_list, line) == NULL) {
239 /* add directory content to list */
240 add_dir(gcmp, line, dir_list, list, c);
243 #endif
245 static int
246 handle_add_to_playlist(struct mpdclient *c)
248 #ifndef NCMPC_MINI
249 /* initialize completion support */
250 GCompletion *gcmp = g_completion_new(NULL);
251 g_completion_set_compare(gcmp, completion_strncmp);
253 GList *list = NULL;
254 GList *dir_list = NULL;
255 completion_callback_data_t data = {
256 .list = &list,
257 .dir_list = &dir_list,
258 .c = c,
261 wrln_completion_callback_data = &data;
262 wrln_pre_completion_callback = add_pre_completion_cb;
263 wrln_post_completion_callback = add_post_completion_cb;
264 #else
265 GCompletion *gcmp = NULL;
266 #endif
268 /* get path */
269 char *path = screen_readln(_("Add"),
270 NULL,
271 NULL,
272 gcmp);
274 /* destroy completion data */
275 #ifndef NCMPC_MINI
276 wrln_completion_callback_data = NULL;
277 wrln_pre_completion_callback = NULL;
278 wrln_post_completion_callback = NULL;
279 g_completion_free(gcmp);
280 string_list_free(list);
281 string_list_free(dir_list);
282 #endif
284 /* add the path to the playlist */
285 if (path != NULL) {
286 char *path_utf8 = locale_to_utf8(path);
287 mpdclient_cmd_add_path(c, path_utf8);
288 g_free(path_utf8);
291 g_free(path);
292 return 0;
295 static void
296 screen_queue_init(WINDOW *w, unsigned cols, unsigned rows)
298 lw = list_window_init(w, cols, rows);
300 #ifndef NCMPC_MINI
301 if (options.scroll)
302 hscroll_init(&hscroll, w, options.scroll_sep);
303 #endif
306 static gboolean
307 timer_hide_cursor(gpointer data)
309 struct mpdclient *c = data;
311 assert(options.hide_cursor > 0);
312 assert(timer_hide_cursor_id != 0);
314 timer_hide_cursor_id = 0;
316 /* hide the cursor when mpd is playing and the user is inactive */
318 if (c->status != NULL &&
319 mpd_status_get_state(c->status) == MPD_STATE_PLAY) {
320 lw->hide_cursor = true;
321 screen_queue_repaint();
322 } else
323 timer_hide_cursor_id = g_timeout_add_seconds(options.hide_cursor,
324 timer_hide_cursor, c);
326 return FALSE;
329 static void
330 screen_queue_open(struct mpdclient *c)
332 playlist = &c->playlist;
334 assert(timer_hide_cursor_id == 0);
335 if (options.hide_cursor > 0) {
336 lw->hide_cursor = false;
337 timer_hide_cursor_id = g_timeout_add_seconds(options.hide_cursor,
338 timer_hide_cursor, c);
341 screen_queue_restore_selection();
342 screen_queue_song_change(c->status);
345 static void
346 screen_queue_close(void)
348 if (timer_hide_cursor_id != 0) {
349 g_source_remove(timer_hide_cursor_id);
350 timer_hide_cursor_id = 0;
353 #ifndef NCMPC_MINI
354 if (options.scroll)
355 hscroll_clear(&hscroll);
356 #endif
359 static void
360 screen_queue_resize(unsigned cols, unsigned rows)
362 list_window_resize(lw, cols, rows);
366 static void
367 screen_queue_exit(void)
369 list_window_free(lw);
372 static const char *
373 screen_queue_title(char *str, size_t size)
375 if (options.host == NULL)
376 return _("Queue");
378 g_snprintf(str, size, _("Queue on %s"), options.host);
379 return str;
382 static void
383 screen_queue_paint_callback(WINDOW *w, unsigned i,
384 unsigned y, unsigned width,
385 bool selected, gcc_unused const void *data)
387 assert(playlist != NULL);
388 assert(i < playlist_length(playlist));
390 const struct mpd_song *song = playlist_get(playlist, i);
392 struct hscroll *row_hscroll = NULL;
393 #ifndef NCMPC_MINI
394 row_hscroll = selected && options.scroll && lw->selected == i
395 ? &hscroll : NULL;
396 #endif
398 paint_song_row(w, y, width, selected,
399 (int)mpd_song_get_id(song) == current_song_id,
400 song, row_hscroll, options.list_format);
403 static void
404 screen_queue_paint(void)
406 #ifndef NCMPC_MINI
407 if (options.scroll)
408 hscroll_clear(&hscroll);
409 #endif
411 list_window_paint2(lw, screen_queue_paint_callback, NULL);
414 static void
415 screen_queue_update(struct mpdclient *c)
417 if (c->events & MPD_IDLE_QUEUE)
418 screen_queue_restore_selection();
419 else
420 /* the queue size may have changed, even if we havn't
421 received the QUEUE idle event yet */
422 list_window_set_length(lw, playlist_length(playlist));
424 if (((c->events & MPD_IDLE_PLAYER) != 0 &&
425 screen_queue_song_change(c->status)) ||
426 c->events & MPD_IDLE_QUEUE)
427 /* the queue or the current song has changed, we must
428 paint the new version */
429 screen_queue_paint();
432 #ifdef HAVE_GETMOUSE
433 static bool
434 handle_mouse_event(struct mpdclient *c)
436 unsigned long bstate;
437 int row;
438 if (screen_get_mouse_event(c, &bstate, &row) ||
439 list_window_mouse(lw, bstate, row)) {
440 screen_queue_paint();
441 return true;
444 if (bstate & BUTTON1_DOUBLE_CLICKED) {
445 /* stop */
446 screen_cmd(c, CMD_STOP);
447 return true;
450 const unsigned old_selected = lw->selected;
451 list_window_set_cursor(lw, lw->start + row);
453 if (bstate & BUTTON1_CLICKED) {
454 /* play */
455 const struct mpd_song *song = screen_queue_selected_song();
456 if (song != NULL) {
457 struct mpd_connection *connection =
458 mpdclient_get_connection(c);
460 if (connection != NULL &&
461 !mpd_run_play_id(connection,
462 mpd_song_get_id(song)))
463 mpdclient_handle_error(c);
465 } else if (bstate & BUTTON3_CLICKED) {
466 /* delete */
467 if (lw->selected == old_selected)
468 mpdclient_cmd_delete(c, lw->selected);
470 list_window_set_length(lw, playlist_length(playlist));
473 screen_queue_save_selection();
474 screen_queue_paint();
476 return true;
478 #endif
480 static bool
481 screen_queue_cmd(struct mpdclient *c, command_t cmd)
483 struct mpd_connection *connection;
484 static command_t cached_cmd = CMD_NONE;
486 const command_t prev_cmd = cached_cmd;
487 cached_cmd = cmd;
489 lw->hide_cursor = false;
491 if (options.hide_cursor > 0) {
492 if (timer_hide_cursor_id != 0)
493 g_source_remove(timer_hide_cursor_id);
494 timer_hide_cursor_id = g_timeout_add_seconds(options.hide_cursor,
495 timer_hide_cursor, c);
498 if (list_window_cmd(lw, cmd)) {
499 screen_queue_save_selection();
500 screen_queue_paint();
501 return true;
504 switch(cmd) {
505 case CMD_SCREEN_UPDATE:
506 center_playing_item(c->status, prev_cmd == CMD_SCREEN_UPDATE);
507 screen_queue_paint();
508 return false;
509 case CMD_SELECT_PLAYING:
510 list_window_set_cursor(lw, playlist_get_index(&c->playlist,
511 c->song));
512 screen_queue_save_selection();
513 screen_queue_paint();
514 return true;
516 case CMD_LIST_FIND:
517 case CMD_LIST_RFIND:
518 case CMD_LIST_FIND_NEXT:
519 case CMD_LIST_RFIND_NEXT:
520 screen_find(lw, cmd, screen_queue_lw_callback, NULL);
521 screen_queue_save_selection();
522 screen_queue_paint();
523 return true;
524 case CMD_LIST_JUMP:
525 screen_jump(lw, screen_queue_lw_callback, NULL, NULL, NULL);
526 screen_queue_save_selection();
527 screen_queue_paint();
528 return true;
530 #ifdef HAVE_GETMOUSE
531 case CMD_MOUSE_EVENT:
532 return handle_mouse_event(c);
533 #endif
535 #ifdef ENABLE_SONG_SCREEN
536 case CMD_SCREEN_SONG:
537 if (screen_queue_selected_song() != NULL) {
538 screen_song_switch(c, screen_queue_selected_song());
539 return true;
542 break;
543 #endif
545 #ifdef ENABLE_LYRICS_SCREEN
546 case CMD_SCREEN_LYRICS:
547 if (lw->selected < playlist_length(&c->playlist)) {
548 struct mpd_song *selected = playlist_get(&c->playlist, lw->selected);
549 bool follow = false;
551 if (c->song && selected &&
552 !strcmp(mpd_song_get_uri(selected),
553 mpd_song_get_uri(c->song)))
554 follow = true;
556 screen_lyrics_switch(c, selected, follow);
557 return true;
560 break;
561 #endif
562 case CMD_SCREEN_SWAP:
563 if (playlist_length(&c->playlist) > 0)
564 screen_swap(c, playlist_get(&c->playlist, lw->selected));
565 else
566 screen_swap(c, NULL);
567 return true;
569 default:
570 break;
573 if (!mpdclient_is_connected(c))
574 return false;
576 switch(cmd) {
577 const struct mpd_song *song;
578 struct list_window_range range;
580 case CMD_PLAY:
581 song = screen_queue_selected_song();
582 if (song == NULL)
583 return false;
585 connection = mpdclient_get_connection(c);
586 if (connection != NULL &&
587 !mpd_run_play_id(connection, mpd_song_get_id(song)))
588 mpdclient_handle_error(c);
590 return true;
592 case CMD_DELETE:
593 list_window_get_range(lw, &range);
594 mpdclient_cmd_delete_range(c, range.start, range.end);
596 list_window_set_cursor(lw, range.start);
597 return true;
599 case CMD_SAVE_PLAYLIST:
600 playlist_save(c, NULL, NULL);
601 return true;
603 case CMD_ADD:
604 handle_add_to_playlist(c);
605 return true;
607 case CMD_SHUFFLE:
608 list_window_get_range(lw, &range);
609 if (range.end <= range.start + 1)
610 /* No range selection, shuffle all list. */
611 break;
613 connection = mpdclient_get_connection(c);
614 if (connection == NULL)
615 return true;
617 if (mpd_run_shuffle_range(connection, range.start, range.end))
618 screen_status_message(_("Shuffled queue"));
619 else
620 mpdclient_handle_error(c);
621 return true;
623 case CMD_LIST_MOVE_UP:
624 list_window_get_range(lw, &range);
625 if (range.start == 0 || range.end <= range.start)
626 return false;
628 if (!mpdclient_cmd_move(c, range.end - 1, range.start - 1))
629 return true;
631 lw->selected--;
632 lw->range_base--;
634 if (lw->range_selection)
635 list_window_scroll_to(lw, lw->range_base);
636 list_window_scroll_to(lw, lw->selected);
638 screen_queue_save_selection();
639 return true;
641 case CMD_LIST_MOVE_DOWN:
642 list_window_get_range(lw, &range);
643 if (range.end >= playlist_length(&c->playlist))
644 return false;
646 if (!mpdclient_cmd_move(c, range.start, range.end))
647 return true;
649 lw->selected++;
650 lw->range_base++;
652 if (lw->range_selection)
653 list_window_scroll_to(lw, lw->range_base);
654 list_window_scroll_to(lw, lw->selected);
656 screen_queue_save_selection();
657 return true;
659 case CMD_LOCATE:
660 if (screen_queue_selected_song() != NULL) {
661 screen_file_goto_song(c, screen_queue_selected_song());
662 return true;
665 break;
667 default:
668 break;
671 return false;
674 const struct screen_functions screen_queue = {
675 .init = screen_queue_init,
676 .exit = screen_queue_exit,
677 .open = screen_queue_open,
678 .close = screen_queue_close,
679 .resize = screen_queue_resize,
680 .paint = screen_queue_paint,
681 .update = screen_queue_update,
682 .cmd = screen_queue_cmd,
683 .get_title = screen_queue_title,