Code

screen_play: moved g_strdup_printf() call to format_duration()
[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
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                 if(lw->range_selection < 0)
85                 {
86                         lw->selected = *((int *) data);
87                         if (lw->selected < lw->start)
88                                 lw->start--;
89                 }
90                 break;
91         default:
92                 break;
93         }
95         list_window_check_selected(lw, c->playlist.list->len);
96         playlist_repaint_if_active();
97 }
99 static char *
100 format_duration(int duration)
102         return g_strdup_printf("%d:%02d", duration / 60, duration % 60);
105 static const char *
106 list_callback(unsigned idx, bool *highlight, char **second_column, G_GNUC_UNUSED void *data)
108         static char songname[MAX_SONG_LENGTH];
109 #ifndef NCMPC_MINI
110         static scroll_state_t st;
111 #endif
112         mpd_Song *song;
114         if (playlist == NULL || idx >= playlist_length(playlist))
115                 return NULL;
117         song = playlist_get(playlist, idx);
118         if (song->id == current_song_id)
119                 *highlight = true;
121         strfsong(songname, MAX_SONG_LENGTH, options.list_format, song);
123 #ifndef NCMPC_MINI
124         if(second_column)
125                 *second_column = format_duration(song->time);
127         if ((unsigned)song->pos == lw->selected)
128         {
129                 if (options.scroll && utf8_width(songname) > (unsigned)COLS)
130                 {
131                         static unsigned current_song;
132                         char *tmp;
134                         if (current_song != lw->selected) {
135                                 st.offset = 0;
136                                 current_song = lw->selected;
137                         }
139                         tmp = strscroll(songname, options.scroll_sep,
140                                         MAX_SONG_LENGTH, &st);
141                         g_strlcpy(songname, tmp, MAX_SONG_LENGTH);
142                         g_free(tmp);
143                 }
144                 else
145                         st.offset = 0;
146         }
147 #else
148         (void)second_column;
149 #endif
151         return songname;
154 static void
155 center_playing_item(mpdclient_t *c, bool center_cursor)
157         unsigned length = c->playlist.list->len;
158         int idx;
160         if (!c->song || c->status == NULL ||
161                 IS_STOPPED(c->status->state))
162                 return;
164         /* try to center the song that are playing */
165         idx = playlist_get_index(c, c->song);
166         if (idx < 0)
167                 return;
169         if (length < lw->rows)
170         {
171                 if (center_cursor)
172                         list_window_set_selected(lw, idx);
173                 return;
174         }
176         list_window_center(lw, length, idx);
178         if (center_cursor) {
179                 list_window_set_selected(lw, idx);
180                 return;
181         }
183         /* make sure the cursor is in the window */
184         if (lw->selected < lw->start + options.scroll_offset) {
185                 if (lw->start > 0)
186                         lw->selected = lw->start + options.scroll_offset;
187                 if (lw->range_selection) {
188                         lw->selected_start = lw->range_base;
189                         lw->selected_end = lw->selected;
190                 } else {
191                         lw->selected_start = lw->selected;
192                         lw->selected_end = lw->selected;
193                 }
194         } else if (lw->selected > lw->start + lw->rows - 1 - options.scroll_offset) {
195                 if (lw->start + lw->rows < length)
196                         lw->selected = lw->start + lw->rows - 1 - options.scroll_offset;
197                 if (lw->range_selection) {
198                         lw->selected_start = lw->selected;
199                         lw->selected_end = lw->range_base;
200                 } else {
201                         lw->selected_start = lw->selected;
202                         lw->selected_end = lw->selected;
203                 }
204         }
207 #ifndef NCMPC_MINI
208 static void
209 save_pre_completion_cb(GCompletion *gcmp, G_GNUC_UNUSED gchar *line,
210                        void *data)
212         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
213         GList **list = tmp->list;
214         mpdclient_t *c = tmp->c;
216         if( *list == NULL ) {
217                 /* create completion list */
218                 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_PLAYLIST);
219                 g_completion_add_items(gcmp, *list);
220         }
223 static void
224 save_post_completion_cb(G_GNUC_UNUSED GCompletion *gcmp,
225                         G_GNUC_UNUSED gchar *line, GList *items,
226                         G_GNUC_UNUSED void *data)
228         if (g_list_length(items) >= 1)
229                 screen_display_completion_list(items);
231 #endif
233 #ifndef NCMPC_MINI
234 /**
235  * Wrapper for strncmp().  We are not allowed to pass &strncmp to
236  * g_completion_set_compare(), because strncmp() takes size_t where
237  * g_completion_set_compare passes a gsize value.
238  */
239 static gint
240 completion_strncmp(const gchar *s1, const gchar *s2, gsize n)
242         return strncmp(s1, s2, n);
244 #endif
246 int
247 playlist_save(mpdclient_t *c, char *name, char *defaultname)
249         gchar *filename, *filename_utf8;
250         gint error;
251 #ifndef NCMPC_MINI
252         GCompletion *gcmp;
253         GList *list = NULL;
254         completion_callback_data_t data;
255 #endif
257 #ifdef NCMPC_MINI
258         (void)defaultname;
259 #endif
261 #ifndef NCMPC_MINI
262         if (name == NULL) {
263                 /* initialize completion support */
264                 gcmp = g_completion_new(NULL);
265                 g_completion_set_compare(gcmp, completion_strncmp);
266                 data.list = &list;
267                 data.dir_list = NULL;
268                 data.c = c;
269                 wrln_completion_callback_data = &data;
270                 wrln_pre_completion_callback = save_pre_completion_cb;
271                 wrln_post_completion_callback = save_post_completion_cb;
274                 /* query the user for a filename */
275                 filename = screen_readln(screen.status_window.w,
276                                          _("Save playlist as"),
277                                          defaultname,
278                                          NULL,
279                                          gcmp);
281                 /* destroy completion support */
282                 wrln_completion_callback_data = NULL;
283                 wrln_pre_completion_callback = NULL;
284                 wrln_post_completion_callback = NULL;
285                 g_completion_free(gcmp);
286                 list = string_list_free(list);
287                 if( filename )
288                         filename=g_strstrip(filename);
289         } else
290 #endif
291                         filename=g_strdup(name);
293         if (filename == NULL)
294                 return -1;
296         /* send save command to mpd */
298         filename_utf8 = locale_to_utf8(filename);
299         error = mpdclient_cmd_save_playlist(c, filename_utf8);
300         g_free(filename_utf8);
302         if (error) {
303                 gint code = GET_ACK_ERROR_CODE(error);
305                 if (code == MPD_ACK_ERROR_EXIST) {
306                         char *buf;
307                         int key;
309                         buf = g_strdup_printf(_("Replace %s [%s/%s] ? "),
310                                               filename, YES, NO);
311                         key = tolower(screen_getch(screen.status_window.w,
312                                                    buf));
313                         g_free(buf);
315                         if (key == YES[0]) {
316                                 filename_utf8 = locale_to_utf8(filename);
317                                 error = mpdclient_cmd_delete_playlist(c, filename_utf8);
318                                 g_free(filename_utf8);
320                                 if (error) {
321                                         g_free(filename);
322                                         return -1;
323                                 }
325                                 error = playlist_save(c, filename, NULL);
326                                 g_free(filename);
327                                 return error;
328                         }
330                         screen_status_printf(_("Aborted"));
331                 }
333                 g_free(filename);
334                 return -1;
335         }
337         /* success */
338         screen_status_printf(_("Saved %s"), filename);
339         g_free(filename);
340         return 0;
343 #ifndef NCMPC_MINI
344 static void add_dir(GCompletion *gcmp, gchar *dir, GList **dir_list,
345                     GList **list, mpdclient_t *c)
347         g_completion_remove_items(gcmp, *list);
348         *list = string_list_remove(*list, dir);
349         *list = gcmp_list_from_path(c, dir, *list, GCMP_TYPE_RFILE);
350         g_completion_add_items(gcmp, *list);
351         *dir_list = g_list_append(*dir_list, g_strdup(dir));
354 static void add_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
356         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
357         GList **dir_list = tmp->dir_list;
358         GList **list = tmp->list;
359         mpdclient_t *c = tmp->c;
361         if (*list == NULL) {
362                 /* create initial list */
363                 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_RFILE);
364                 g_completion_add_items(gcmp, *list);
365         } else if (line && line[0] && line[strlen(line)-1]=='/' &&
366                    string_list_find(*dir_list, line) == NULL) {
367                 /* add directory content to list */
368                 add_dir(gcmp, line, dir_list, list, c);
369         }
372 static void add_post_completion_cb(GCompletion *gcmp, gchar *line,
373                                    GList *items, void *data)
375         completion_callback_data_t *tmp = (completion_callback_data_t *)data;
376         GList **dir_list = tmp->dir_list;
377         GList **list = tmp->list;
378         mpdclient_t *c = tmp->c;
380         if (g_list_length(items) >= 1)
381                 screen_display_completion_list(items);
383         if (line && line[0] && line[strlen(line) - 1] == '/' &&
384             string_list_find(*dir_list, line) == NULL) {
385                 /* add directory content to list */
386                 add_dir(gcmp, line, dir_list, list, c);
387         }
389 #endif
391 static int
392 handle_add_to_playlist(mpdclient_t *c)
394         gchar *path;
395 #ifndef NCMPC_MINI
396         GCompletion *gcmp;
397         GList *list = NULL;
398         GList *dir_list = NULL;
399         completion_callback_data_t data;
401         /* initialize completion support */
402         gcmp = g_completion_new(NULL);
403         g_completion_set_compare(gcmp, completion_strncmp);
404         data.list = &list;
405         data.dir_list = &dir_list;
406         data.c = c;
407         wrln_completion_callback_data = &data;
408         wrln_pre_completion_callback = add_pre_completion_cb;
409         wrln_post_completion_callback = add_post_completion_cb;
410 #endif
412         /* get path */
413         path = screen_readln(screen.status_window.w,
414                              _("Add"),
415                              NULL,
416                              NULL,
417 #ifdef NCMPC_MINI
418                              NULL
419 #else
420                              gcmp
421 #endif
422                              );
424         /* destroy completion data */
425 #ifndef NCMPC_MINI
426         wrln_completion_callback_data = NULL;
427         wrln_pre_completion_callback = NULL;
428         wrln_post_completion_callback = NULL;
429         g_completion_free(gcmp);
430         string_list_free(list);
431         string_list_free(dir_list);
432 #endif
434         /* add the path to the playlist */
435         if (path != NULL) {
436                 char *path_utf8 = locale_to_utf8(path);
437                 mpdclient_cmd_add_path(c, path_utf8);
438                 g_free(path_utf8);
439         }
441         g_free(path);
442         return 0;
445 static void
446 play_init(WINDOW *w, int cols, int rows)
448         lw = list_window_init(w, cols, rows);
451 static gboolean
452 timer_hide_cursor(gpointer data)
454         struct mpdclient *c = data;
456         assert(options.hide_cursor > 0);
457         assert(timer_hide_cursor_id != 0);
459         timer_hide_cursor_id = 0;
461         /* hide the cursor when mpd is playing and the user is inactive */
463         if (c->status != NULL && c->status->state == MPD_STATUS_STATE_PLAY) {
464                 lw->hide_cursor = true;
465                 playlist_repaint();
466         } else
467                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
468                                                      timer_hide_cursor, c);
470         return FALSE;
473 static void
474 play_open(mpdclient_t *c)
476         static gboolean install_cb = TRUE;
478         playlist = &c->playlist;
480         assert(timer_hide_cursor_id == 0);
481         if (options.hide_cursor > 0) {
482                 lw->hide_cursor = false;
483                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
484                                                      timer_hide_cursor, c);
485         }
487         if (install_cb) {
488                 mpdclient_install_playlist_callback(c, playlist_changed_callback);
489                 install_cb = FALSE;
490         }
493 static void
494 play_close(void)
496         if (timer_hide_cursor_id != 0) {
497                 g_source_remove(timer_hide_cursor_id);
498                 timer_hide_cursor_id = 0;
499         }
502 static void
503 play_resize(int cols, int rows)
505         lw->cols = cols;
506         lw->rows = rows;
510 static void
511 play_exit(void)
513         list_window_free(lw);
516 static const char *
517 play_title(char *str, size_t size)
519         if( strcmp(options.host, "localhost") == 0 )
520                 return _("Playlist");
522         g_snprintf(str, size, _("Playlist on %s"), options.host);
523         return str;
526 static void
527 play_paint(void)
529         list_window_paint(lw, list_callback, NULL);
532 static void
533 play_update(mpdclient_t *c)
535         static int prev_song_id = -1;
537         current_song_id = c->song != NULL && c->status != NULL &&
538                 !IS_STOPPED(c->status->state) ? c->song->id : -1;
540         if (current_song_id != prev_song_id) {
541                 prev_song_id = current_song_id;
543                 /* center the cursor */
544                 if (options.auto_center && current_song_id != -1 && ! lw->range_selection)
545                         center_playing_item(c, false);
547                 playlist_repaint();
548 #ifndef NCMPC_MINI
549         } else if (options.scroll) {
550                 /* always repaint if horizontal scrolling is
551                    enabled */
552                 playlist_repaint();
553 #endif
554         }
557 #ifdef HAVE_GETMOUSE
558 static bool
559 handle_mouse_event(struct mpdclient *c)
561         int row;
562         unsigned selected;
563         unsigned long bstate;
565         if (screen_get_mouse_event(c, &bstate, &row) ||
566             list_window_mouse(lw, playlist_length(playlist), bstate, row)) {
567                 playlist_repaint();
568                 return true;
569         }
571         if (bstate & BUTTON1_DOUBLE_CLICKED) {
572                 /* stop */
573                 screen_cmd(c, CMD_STOP);
574                 return true;
575         }
577         selected = lw->start + row;
579         if (bstate & BUTTON1_CLICKED) {
580                 /* play */
581                 if (lw->start + row < playlist_length(playlist))
582                         mpdclient_cmd_play(c, lw->start + row);
583         } else if (bstate & BUTTON3_CLICKED) {
584                 /* delete */
585                 if (selected == lw->selected)
586                         mpdclient_cmd_delete(c, lw->selected);
587         }
589         lw->selected = selected;
590         list_window_check_selected(lw, playlist_length(playlist));
591         playlist_repaint();
593         return true;
595 #endif
597 static bool
598 play_cmd(mpdclient_t *c, command_t cmd)
600         static command_t cached_cmd = CMD_NONE;
601         command_t prev_cmd = cached_cmd;
602         cached_cmd = cmd;
604         lw->hide_cursor = false;
606         if (options.hide_cursor > 0) {
607                 if (timer_hide_cursor_id != 0)
608                         g_source_remove(timer_hide_cursor_id);
609                 timer_hide_cursor_id = g_timeout_add(options.hide_cursor * 1000,
610                                                      timer_hide_cursor, c);
611         }
613         if (list_window_cmd(lw, playlist_length(&c->playlist), cmd)) {
614                 playlist_repaint();
615                 return true;
616         }
618         switch(cmd) {
619         case CMD_PLAY:
620                 mpdclient_cmd_play(c, lw->selected);
621                 return true;
622         case CMD_DELETE:
623         {
624                 int i = lw->selected_end, start = lw->selected_start;
625                 for(; i >= start; --i)
626                         mpdclient_cmd_delete(c, i);
628                 i++;
629                 if(i >= (int)playlist_length(&c->playlist))
630                         i--;
631                 lw->selected = i;
632                 lw->selected_start = i;
633                 lw->selected_end = i;
634                 lw->range_selection = false;
636                 return true;
637         }
638         case CMD_SAVE_PLAYLIST:
639                 playlist_save(c, NULL, NULL);
640                 return true;
641         case CMD_ADD:
642                 handle_add_to_playlist(c);
643                 return true;
644         case CMD_SCREEN_UPDATE:
645                 center_playing_item(c, prev_cmd == CMD_SCREEN_UPDATE);
646                 playlist_repaint();
647                 return false;
648         case CMD_SELECT_PLAYING:
649                 list_window_set_selected(lw, playlist_get_index(c, c->song));
650                 return true;
651         case CMD_SHUFFLE:
652         {
653                 if(!lw->range_selection)
654                         /* No range selection, shuffle all list. */
655                         break;
657                 if (mpdclient_cmd_shuffle_range(c, lw->selected_start, lw->selected_end+1) == 0)
658                         screen_status_message(_("Shuffled playlist"));
660                 return true;
661         }
662         case CMD_LIST_MOVE_UP:
663                 if(lw->selected_start == 0)
664                         return false;
665                 if(lw->range_selection)
666                 {
667                         unsigned i = lw->selected_start;
668                         unsigned last_selected = lw->selected;
669                         for(; i <= lw->selected_end; ++i)
670                                 mpdclient_cmd_move(c, i, i-1);
671                         lw->selected_start--;
672                         lw->selected_end--;
673                         lw->selected = last_selected - 1;
674                         lw->range_base--;
675                 }
676                 else
677                 {
678                         mpdclient_cmd_move(c, lw->selected, lw->selected-1);
679                         lw->selected--;
680                         lw->selected_start--;
681                         lw->selected_end--;
682                 }
683                 return true;
684         case CMD_LIST_MOVE_DOWN:
685                 if(lw->selected_end+1 >= playlist_length(&c->playlist))
686                         return false;
687                 if(lw->range_selection)
688                 {
689                         int i = lw->selected_end;
690                         unsigned last_selected = lw->selected;
691                         for(; i >= (int)lw->selected_start; --i)
692                                 mpdclient_cmd_move(c, i, i+1);
693                         lw->selected_start++;
694                         lw->selected_end++;
695                         lw->selected = last_selected + 1;
696                         lw->range_base++;
697                 }
698                 else
699                 {
700                         mpdclient_cmd_move(c, lw->selected, lw->selected+1);
701                         lw->selected++;
702                         lw->selected_start++;
703                         lw->selected_end++;
704                 }
705                 return true;
706         case CMD_LIST_FIND:
707         case CMD_LIST_RFIND:
708         case CMD_LIST_FIND_NEXT:
709         case CMD_LIST_RFIND_NEXT:
710                 screen_find(lw, playlist_length(&c->playlist),
711                             cmd, list_callback, NULL);
712                 playlist_repaint();
713                 return true;
714         case CMD_LIST_JUMP:
715                 screen_jump(lw, list_callback, NULL);
716                 playlist_repaint();
717                 return true;
719 #ifdef HAVE_GETMOUSE
720         case CMD_MOUSE_EVENT:
721                 return handle_mouse_event(c);
722 #endif
724 #ifdef ENABLE_SONG_SCREEN
725         case CMD_SCREEN_SONG:
726                 if (lw->selected < playlist_length(&c->playlist)) {
727                         screen_song_switch(c, playlist_get(&c->playlist, lw->selected));
728                         return true;
729                 }
731                 break;
732 #endif
734         case CMD_LOCATE:
735                 if (lw->selected < playlist_length(&c->playlist)) {
736                         screen_file_goto_song(c, playlist_get(&c->playlist, lw->selected));
737                         return true;
738                 }
740                 break;
742 #ifdef ENABLE_LYRICS_SCREEN
743         case CMD_SCREEN_LYRICS:
744                 if (lw->selected < playlist_length(&c->playlist)) {
745                         screen_lyrics_switch(c, playlist_get(&c->playlist, lw->selected));
746                         return true;
747                 }
749                 break;
750 #endif
751         case CMD_SCREEN_SWAP:
752                 screen_swap(c, playlist_get(&c->playlist, lw->selected));
753                 return true;
755         default:
756                 break;
757         }
759         return false;
762 const struct screen_functions screen_playlist = {
763         .init = play_init,
764         .exit = play_exit,
765         .open = play_open,
766         .close = play_close,
767         .resize = play_resize,
768         .paint = play_paint,
769         .update = play_update,
770         .cmd = play_cmd,
771         .get_title = play_title,
772 };