Code

screen_queue: move playlist_save() to save_playlist.c
[ncmpc.git] / src / screen_queue.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2017 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
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.
9  *
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.
14  *
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.
18  */
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
55 {
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)
75 {
76         screen_queue_paint();
77         wrefresh(lw->w);
78 }
80 static const struct mpd_song *
81 screen_queue_selected_song(void)
82 {
83         return !lw->range_selection &&
84                 lw->selected < playlist_length(playlist)
85                 ? playlist_get(playlist, lw->selected)
86                 : NULL;
87 }
89 static void
90 screen_queue_save_selection(void)
91 {
92         selected_song_id = screen_queue_selected_song() != NULL
93                 ? (int)mpd_song_get_id(screen_queue_selected_song())
94                 : -1;
95 }
97 static void
98 screen_queue_restore_selection(void)
99 {
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;
152         }
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
185 /**
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.
189  */
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);
223         }
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);
241         }
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,
259         };
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);
289         }
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);
339         }
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;
351         }
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;
442         }
444         if (bstate & BUTTON1_DOUBLE_CLICKED) {
445                 /* stop */
446                 screen_cmd(c, CMD_STOP);
447                 return true;
448         }
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);
464                 }
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));
471         }
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);
496         }
498         if (list_window_cmd(lw, cmd)) {
499                 screen_queue_save_selection();
500                 screen_queue_paint();
501                 return true;
502         }
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;
540                 }
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;
558                 }
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;
571         }
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;
663                 }
665                 break;
667         default:
668                 break;
669         }
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,
684 };