Code

eae3ace8c50098fbc5f2ab1b7bb5ce3bb9df2853
[ncmpc.git] / src / screen_play.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 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.
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.
18 */
20 #include "config.h"
21 #include "i18n.h"
22 #include "charset.h"
23 #include "options.h"
24 #include "mpdclient.h"
25 #include "utils.h"
26 #include "strfsong.h"
27 #include "wreadln.h"
28 #include "command.h"
29 #include "colors.h"
30 #include "screen.h"
31 #include "screen_utils.h"
32 #include "screen_play.h"
34 #ifndef NCMPC_MINI
35 #include "hscroll.h"
36 #endif
38 #include <ctype.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <glib.h>
44 #define MAX_SONG_LENGTH 512
46 #ifndef NCMPC_MINI
47 typedef struct
48 {
49         GList **list;
50         GList **dir_list;
51         mpdclient_t *c;
52 } completion_callback_data_t;
53 #endif
55 static struct mpdclient_playlist *playlist;
56 static int current_song_id = -1;
57 static list_window_t *lw = NULL;
58 static guint timer_hide_cursor_id;
60 static void
61 play_paint(void);
63 static void
64 playlist_repaint(void)
65 {
66         play_paint();
67         wrefresh(lw->w);
68 }
70 static void
71 playlist_repaint_if_active(void)
72 {
73         if (screen_is_visible(&screen_playlist))
74                 playlist_repaint();
75 }
77 static void
78 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
79 {
80         switch (event) {
81         case PLAYLIST_EVENT_DELETE:
82                 break;
83         case PLAYLIST_EVENT_MOVE:
84                 lw->selected = *((int *) data);
85                 if (lw->selected < lw->start)
86                         lw->start--;
87                 break;
88         default:
89                 break;
90         }
92         list_window_check_selected(lw, c->playlist.list->len);
93         playlist_repaint_if_active();
94 }
96 static const char *
97 list_callback(unsigned idx, bool *highlight, G_GNUC_UNUSED void *data)
98 {
99         static char songname[MAX_SONG_LENGTH];
100 #ifndef NCMPC_MINI
101         static scroll_state_t st;
102 #endif
103         mpd_Song *song;
105         if (playlist == NULL || idx >= playlist_length(playlist))
106                 return NULL;
108         song = playlist_get(playlist, idx);
109         if (song->id == current_song_id)
110                 *highlight = true;
112         strfsong(songname, MAX_SONG_LENGTH, options.list_format, song);
114 #ifndef NCMPC_MINI
115         if (options.scroll && (unsigned)song->pos == lw->selected &&
116             utf8_width(songname) > (unsigned)COLS) {
117                 static unsigned current_song;
118                 char *tmp;
120                 if (current_song != lw->selected) {
121                         st.offset = 0;
122                         current_song = lw->selected;
123                 }
125                 tmp = strscroll(songname, options.scroll_sep,
126                                 MAX_SONG_LENGTH, &st);
127                 g_strlcpy(songname, tmp, MAX_SONG_LENGTH);
128                 g_free(tmp);
129         } else if ((unsigned)song->pos == lw->selected)
130                 st.offset = 0;
131 #endif
133         return songname;
136 static void
137 center_playing_item(mpdclient_t *c)
139         unsigned length = c->playlist.list->len;
140         unsigned offset = lw->selected - lw->start;
141         int idx;
143         if (!c->song || length < lw->rows ||
144             c->status == NULL || IS_STOPPED(c->status->state))
145                 return;
147         /* try to center the song that are playing */
148         idx = playlist_get_index(c, c->song);
149         if (idx < 0)
150                 return;
152         list_window_center(lw, length, idx);
154         /* make sure the cursor is in the window */
155         lw->selected = lw->start+offset;
156         list_window_check_selected(lw, length);
159 #ifndef NCMPC_MINI
160 static void
161 save_pre_completion_cb(GCompletion *gcmp, G_GNUC_UNUSED gchar *line,
162                        void *data)
164         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
165         GList **list = tmp->list;
166         mpdclient_t *c = tmp->c;
168         if( *list == NULL ) {
169                 /* create completion list */
170                 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_PLAYLIST);
171                 g_completion_add_items(gcmp, *list);
172         }
175 static void
176 save_post_completion_cb(G_GNUC_UNUSED GCompletion *gcmp,
177                         G_GNUC_UNUSED gchar *line, GList *items,
178                         G_GNUC_UNUSED void *data)
180         if (g_list_length(items) >= 1)
181                 screen_display_completion_list(items);
183 #endif
185 #ifndef NCMPC_MINI
186 /**
187  * Wrapper for strncmp().  We are not allowed to pass &strncmp to
188  * g_completion_set_compare(), because strncmp() takes size_t where
189  * g_completion_set_compare passes a gsize value.
190  */
191 static gint
192 completion_strncmp(const gchar *s1, const gchar *s2, gsize n)
194         return strncmp(s1, s2, n);
196 #endif
198 int
199 playlist_save(mpdclient_t *c, char *name, char *defaultname)
201         gchar *filename, *filename_utf8;
202         gint error;
203 #ifndef NCMPC_MINI
204         GCompletion *gcmp;
205         GList *list = NULL;
206         completion_callback_data_t data;
207 #endif
209 #ifdef NCMPC_MINI
210         (void)defaultname;
211 #endif
213 #ifndef NCMPC_MINI
214         if (name == NULL) {
215                 /* initialize completion support */
216                 gcmp = g_completion_new(NULL);
217                 g_completion_set_compare(gcmp, completion_strncmp);
218                 data.list = &list;
219                 data.dir_list = NULL;
220                 data.c = c;
221                 wrln_completion_callback_data = &data;
222                 wrln_pre_completion_callback = save_pre_completion_cb;
223                 wrln_post_completion_callback = save_post_completion_cb;
226                 /* query the user for a filename */
227                 filename = screen_readln(screen.status_window.w,
228                                          _("Save playlist as: "),
229                                          defaultname,
230                                          NULL,
231                                          gcmp);
233                 /* destroy completion support */
234                 wrln_completion_callback_data = NULL;
235                 wrln_pre_completion_callback = NULL;
236                 wrln_post_completion_callback = NULL;
237                 g_completion_free(gcmp);
238                 list = string_list_free(list);
239                 if( filename )
240                         filename=g_strstrip(filename);
241         } else
242 #endif
243                         filename=g_strdup(name);
245         if (filename == NULL)
246                 return -1;
248         /* send save command to mpd */
250         filename_utf8 = locale_to_utf8(filename);
251         error = mpdclient_cmd_save_playlist(c, filename_utf8);
252         g_free(filename_utf8);
254         if (error) {
255                 gint code = GET_ACK_ERROR_CODE(error);
257                 if (code == MPD_ACK_ERROR_EXIST) {
258                         char *buf;
259                         int key;
261                         buf = g_strdup_printf(_("Replace %s [%s/%s] ? "),
262                                               filename, YES, NO);
263                         key = tolower(screen_getch(screen.status_window.w,
264                                                    buf));
265                         g_free(buf);
267                         if (key == YES[0]) {
268                                 filename_utf8 = locale_to_utf8(filename);
269                                 error = mpdclient_cmd_delete_playlist(c, filename_utf8);
270                                 g_free(filename_utf8);
272                                 if (error) {
273                                         g_free(filename);
274                                         return -1;
275                                 }
277                                 error = playlist_save(c, filename, NULL);
278                                 g_free(filename);
279                                 return error;
280                         }
282                         screen_status_printf(_("Aborted"));
283                 }
285                 g_free(filename);
286                 return -1;
287         }
289         /* success */
290         screen_status_printf(_("Saved %s"), filename);
291         g_free(filename);
292         return 0;
295 #ifndef NCMPC_MINI
296 static void add_dir(GCompletion *gcmp, gchar *dir, GList **dir_list,
297                     GList **list, mpdclient_t *c)
299         g_completion_remove_items(gcmp, *list);
300         *list = string_list_remove(*list, dir);
301         *list = gcmp_list_from_path(c, dir, *list, GCMP_TYPE_RFILE);
302         g_completion_add_items(gcmp, *list);
303         *dir_list = g_list_append(*dir_list, g_strdup(dir));
306 static void add_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
308         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
309         GList **dir_list = tmp->dir_list;
310         GList **list = tmp->list;
311         mpdclient_t *c = tmp->c;
313         if (*list == NULL) {
314                 /* create initial list */
315                 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_RFILE);
316                 g_completion_add_items(gcmp, *list);
317         } else if (line && line[0] && line[strlen(line)-1]=='/' &&
318                    string_list_find(*dir_list, line) == NULL) {
319                 /* add directory content to list */
320                 add_dir(gcmp, line, dir_list, list, c);
321         }
324 static void add_post_completion_cb(GCompletion *gcmp, gchar *line,
325                                    GList *items, void *data)
327         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
328         GList **dir_list = tmp->dir_list;
329         GList **list = tmp->list;
330         mpdclient_t *c = tmp->c;
332         if (g_list_length(items) >= 1)
333                 screen_display_completion_list(items);
335         if (line && line[0] && line[strlen(line) - 1] == '/' &&
336             string_list_find(*dir_list, line) == NULL) {
337                 /* add directory content to list */
338                 add_dir(gcmp, line, dir_list, list, c);
339         }
341 #endif
343 static int
344 handle_add_to_playlist(mpdclient_t *c)
346         gchar *path;
347 #ifndef NCMPC_MINI
348         GCompletion *gcmp;
349         GList *list = NULL;
350         GList *dir_list = NULL;
351         completion_callback_data_t data;
353         /* initialize completion support */
354         gcmp = g_completion_new(NULL);
355         g_completion_set_compare(gcmp, completion_strncmp);
356         data.list = &list;
357         data.dir_list = &dir_list;
358         data.c = c;
359         wrln_completion_callback_data = &data;
360         wrln_pre_completion_callback = add_pre_completion_cb;
361         wrln_post_completion_callback = add_post_completion_cb;
362 #endif
364         /* get path */
365         path = screen_readln(screen.status_window.w,
366                              _("Add: "),
367                              NULL,
368                              NULL,
369 #ifdef NCMPC_MINI
370                              NULL
371 #else
372                              gcmp
373 #endif
374                              );
376         /* destroy completion data */
377 #ifndef NCMPC_MINI
378         wrln_completion_callback_data = NULL;
379         wrln_pre_completion_callback = NULL;
380         wrln_post_completion_callback = NULL;
381         g_completion_free(gcmp);
382         string_list_free(list);
383         string_list_free(dir_list);
384 #endif
386         /* add the path to the playlist */
387         if (path != NULL) {
388                 char *path_utf8 = locale_to_utf8(path);
389                 mpdclient_cmd_add_path(c, path_utf8);
390                 g_free(path_utf8);
391         }
393         g_free(path);
394         return 0;
397 static void
398 play_init(WINDOW *w, int cols, int rows)
400         lw = list_window_init(w, cols, rows);
403 static gboolean
404 timer_hide_cursor(gpointer data)
406         struct mpdclient *c = data;
408         assert(options.hide_cursor > 0);
409         assert(timer_hide_cursor_id != 0);
411         timer_hide_cursor_id = 0;
413         /* hide the cursor when mpd is playing and the user is inactive */
415         if (c->status != NULL && c->status->state == MPD_STATUS_STATE_PLAY) {
416                 lw->hide_cursor = true;
417                 playlist_repaint();
418         } else
419                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
420                                                      timer_hide_cursor, c);
422         return FALSE;
425 static void
426 play_open(mpdclient_t *c)
428         static gboolean install_cb = TRUE;
430         playlist = &c->playlist;
432         assert(timer_hide_cursor_id == 0);
433         if (options.hide_cursor > 0) {
434                 lw->hide_cursor = false;
435                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
436                                                      timer_hide_cursor, c);
437         }
439         if (install_cb) {
440                 mpdclient_install_playlist_callback(c, playlist_changed_callback);
441                 install_cb = FALSE;
442         }
445 static void
446 play_close(void)
448         if (timer_hide_cursor_id != 0) {
449                 g_source_remove(timer_hide_cursor_id);
450                 timer_hide_cursor_id = 0;
451         }
454 static void
455 play_resize(int cols, int rows)
457         lw->cols = cols;
458         lw->rows = rows;
462 static void
463 play_exit(void)
465         list_window_free(lw);
468 static const char *
469 play_title(char *str, size_t size)
471         if( strcmp(options.host, "localhost") == 0 )
472                 return _("Playlist");
474         g_snprintf(str, size, _("Playlist on %s"), options.host);
475         return str;
478 static void
479 play_paint(void)
481         list_window_paint(lw, list_callback, NULL);
484 static void
485 play_update(mpdclient_t *c)
487         static int prev_song_id = -1;
489         current_song_id = c->song != NULL && c->status != NULL &&
490                 !IS_STOPPED(c->status->state) ? c->song->id : -1;
492         if (current_song_id != prev_song_id) {
493                 prev_song_id = current_song_id;
495                 /* center the cursor */
496                 if (options.auto_center && current_song_id != -1)
497                         center_playing_item(c);
499                 playlist_repaint();
500 #ifndef NCMPC_MINI
501         } else if (options.scroll) {
502                 /* always repaint if horizontal scrolling is
503                    enabled */
504                 playlist_repaint();
505 #endif
506         }
509 #ifdef HAVE_GETMOUSE
510 static bool
511 handle_mouse_event(struct mpdclient *c)
513         int row;
514         unsigned selected;
515         unsigned long bstate;
517         if (screen_get_mouse_event(c, &bstate, &row) ||
518             list_window_mouse(lw, playlist_length(playlist), bstate, row)) {
519                 playlist_repaint();
520                 return true;
521         }
523         if (bstate & BUTTON1_DOUBLE_CLICKED) {
524                 /* stop */
525                 screen_cmd(c, CMD_STOP);
526                 return true;
527         }
529         selected = lw->start + row;
531         if (bstate & BUTTON1_CLICKED) {
532                 /* play */
533                 if (lw->start + row < playlist_length(playlist))
534                         mpdclient_cmd_play(c, lw->start + row);
535         } else if (bstate & BUTTON3_CLICKED) {
536                 /* delete */
537                 if (selected == lw->selected)
538                         mpdclient_cmd_delete(c, lw->selected);
539         }
541         lw->selected = selected;
542         list_window_check_selected(lw, playlist_length(playlist));
543         playlist_repaint();
545         return true;
547 #endif
549 static bool
550 play_cmd(mpdclient_t *c, command_t cmd)
552         lw->hide_cursor = false;
554         if (options.hide_cursor > 0) {
555                 if (timer_hide_cursor_id != 0)
556                         g_source_remove(timer_hide_cursor_id);
557                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
558                                                      timer_hide_cursor, c);
559         }
561         if (list_window_cmd(lw, playlist_length(&c->playlist), cmd)) {
562                 playlist_repaint();
563                 return true;
564         }
566         switch(cmd) {
567         case CMD_PLAY:
568                 mpdclient_cmd_play(c, lw->selected);
569                 return true;
570         case CMD_DELETE:
571                 mpdclient_cmd_delete(c, lw->selected);
572                 return true;
573         case CMD_SAVE_PLAYLIST:
574                 playlist_save(c, NULL, NULL);
575                 return true;
576         case CMD_ADD:
577                 handle_add_to_playlist(c);
578                 return true;
579         case CMD_SCREEN_UPDATE:
580                 center_playing_item(c);
581                 playlist_repaint();
582                 return false;
584         case CMD_LIST_MOVE_UP:
585                 mpdclient_cmd_move(c, lw->selected, lw->selected-1);
586                 return true;
587         case CMD_LIST_MOVE_DOWN:
588                 mpdclient_cmd_move(c, lw->selected, lw->selected+1);
589                 return true;
590         case CMD_LIST_FIND:
591         case CMD_LIST_RFIND:
592         case CMD_LIST_FIND_NEXT:
593         case CMD_LIST_RFIND_NEXT:
594                 screen_find(lw, playlist_length(&c->playlist),
595                             cmd, list_callback, NULL);
596                 playlist_repaint();
597                 return true;
599 #ifdef HAVE_GETMOUSE
600         case CMD_MOUSE_EVENT:
601                 return handle_mouse_event(c);
602 #endif
604 #ifdef ENABLE_SONG_SCREEN
605         case CMD_VIEW:
606                 if (lw->selected < playlist_length(&c->playlist)) {
607                         screen_song_switch(c, playlist_get(&c->playlist, lw->selected));
608                         return true;
609                 }
611                 break;
612 #endif
614         case CMD_LOCATE:
615                 if (lw->selected < playlist_length(&c->playlist)) {
616                         screen_file_goto_song(c, playlist_get(&c->playlist, lw->selected));
617                         return true;
618                 }
620                 break;
622 #ifdef ENABLE_LYRICS_SCREEN
623         case CMD_SCREEN_LYRICS:
624                 if (lw->selected < playlist_length(&c->playlist)) {
625                         screen_lyrics_switch(c, playlist_get(&c->playlist, lw->selected));
626                         return true;
627                 }
629                 break;
630 #endif
632         default:
633                 break;
634         }
636         return false;
639 const struct screen_functions screen_playlist = {
640         .init = play_init,
641         .exit = play_exit,
642         .open = play_open,
643         .close = play_close,
644         .resize = play_resize,
645         .paint = play_paint,
646         .update = play_update,
647         .cmd = play_cmd,
648         .get_title = play_title,
649 };