1 /*
2 * $Id$
3 *
4 * (c) 2004 by Kalle Wallin <kaw@linux.se>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 #include <glib.h>
26 #include <ncurses.h>
28 #include "config.h"
29 #include "ncmpc.h"
30 #include "options.h"
31 #include "support.h"
32 #include "mpdclient.h"
33 #include "utils.h"
34 #include "strfsong.h"
35 #include "wreadln.h"
36 #include "command.h"
37 #include "colors.h"
38 #include "screen.h"
39 #include "screen_utils.h"
41 #define MAX_SONG_LENGTH 512
43 typedef struct
44 {
45 GList **list;
46 GList **dir_list;
47 screen_t *screen;
48 mpdclient_t *c;
49 } completion_callback_data_t;
51 static list_window_t *lw = NULL;
53 static void
54 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
55 {
56 D("screen_play.c> playlist_callback() [%d]\n", event);
57 switch(event)
58 {
59 case PLAYLIST_EVENT_DELETE:
60 break;
61 case PLAYLIST_EVENT_MOVE:
62 lw->selected = *((int *) data);
63 if( lw->selected<lw->start )
64 lw->start--;
65 break;
66 default:
67 break;
68 }
69 /* make shure the playlist is repainted */
70 lw->clear = 1;
71 lw->repaint = 1;
72 list_window_check_selected(lw, c->playlist.length);
73 }
75 static char *
76 list_callback(int index, int *highlight, void *data)
77 {
78 static char songname[MAX_SONG_LENGTH];
79 mpdclient_t *c = (mpdclient_t *) data;
80 mpd_Song *song;
82 *highlight = 0;
83 if( (song=playlist_get_song(c, index)) == NULL )
84 {
85 return NULL;
86 }
88 if( c->song && song->id==c->song->id && !IS_STOPPED(c->status->state) )
89 {
90 *highlight = 1;
91 }
92 strfsong(songname, MAX_SONG_LENGTH, LIST_FORMAT, song);
93 return songname;
94 }
96 static int
97 center_playing_item(screen_t *screen, mpdclient_t *c)
98 {
99 int length = c->playlist.length;
100 int offset = lw->selected-lw->start;
101 int index;
103 if( !lw || !c->song || length<lw->rows || IS_STOPPED(c->status->state) )
104 return 0;
106 /* try to center the song that are playing */
107 index = playlist_get_index(c, c->song);
108 D("Autocenter song id:%d pos:%d index:%d\n", c->song->id,c->song->pos,index);
109 lw->start = index-(lw->rows/2);
110 if( lw->start+lw->rows > length )
111 lw->start = length-lw->rows;
112 if( lw->start<0 )
113 lw->start=0;
115 /* make sure the cursor is in the window */
116 lw->selected = lw->start+offset;
117 list_window_check_selected(lw, length);
119 lw->clear = 1;
120 lw->repaint = 1;
122 return 0;
123 }
129 void save_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
130 {
131 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
132 GList **list = tmp->list;
133 mpdclient_t *c = tmp->c;
135 if( *list == NULL )
136 {
137 /* create completion list */
138 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_PLAYLIST);
139 g_completion_add_items(gcmp, *list);
140 }
141 }
143 void save_post_completion_cb(GCompletion *gcmp, gchar *line, GList *items,
144 void *data)
145 {
146 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
147 screen_t *screen = tmp->screen;
149 if( g_list_length(items)>=1 )
150 {
151 screen_display_completion_list(screen, items);
152 lw->clear = 1;
153 lw->repaint = 1;
154 }
155 }
156 int
157 playlist_save(screen_t *screen, mpdclient_t *c, char *name, char *defaultname)
158 {
159 gchar *filename;
160 gint error;
161 GCompletion *gcmp;
162 GList *list = NULL;
163 completion_callback_data_t data;
165 if( name==NULL )
166 {
167 /* initialize completion support */
168 gcmp = g_completion_new(NULL);
169 g_completion_set_compare(gcmp, strncmp);
170 data.list = &list;
171 data.dir_list = NULL;
172 data.screen = screen;
173 data.c = c;
174 wrln_completion_callback_data = &data;
175 wrln_pre_completion_callback = save_pre_completion_cb;
176 wrln_post_completion_callback = save_post_completion_cb;
179 /* query the user for a filename */
180 filename = screen_readln(screen->status_window.w,
181 _("Save playlist as: "),
182 defaultname,
183 NULL,
184 gcmp);
186 /* destroy completion support */
187 wrln_completion_callback_data = NULL;
188 wrln_pre_completion_callback = NULL;
189 wrln_post_completion_callback = NULL;
190 g_completion_free(gcmp);
191 list = string_list_free(list);
192 if( filename )
193 filename=g_strstrip(filename);
194 }
195 else
196 {
197 filename=g_strdup(name);
198 }
199 if( filename==NULL || filename[0]=='\0' )
200 return -1;
201 /* send save command to mpd */
202 D("Saving playlist as \'%s \'...\n", filename);
203 if( (error=mpdclient_cmd_save_playlist(c, filename)) )
204 {
205 gint code = GET_ACK_ERROR_CODE(error);
207 if( code == MPD_ACK_ERROR_EXIST )
208 {
209 char *buf;
210 int key;
212 buf=g_strdup_printf(_("Replace %s [%s/%s] ? "), filename, YES, NO);
213 key = tolower(screen_getch(screen->status_window.w, buf));
214 g_free(buf);
215 if( key == YES[0] )
216 {
217 if( mpdclient_cmd_delete_playlist(c, filename) )
218 {
219 g_free(filename);
220 return -1;
221 }
222 error = playlist_save(screen, c, filename, NULL);
223 g_free(filename);
224 return error;
225 }
226 screen_status_printf(_("Aborted!"));
227 }
228 g_free(filename);
229 return -1;
230 }
231 /* success */
232 screen_status_printf(_("Saved %s"), filename);
233 g_free(filename);
234 return 0;
235 }
237 void add_dir(GCompletion *gcmp, gchar *dir, GList **dir_list, GList **list,
238 mpdclient_t *c)
239 {
240 g_completion_remove_items(gcmp, *list);
241 *list = string_list_remove(*list, dir);
242 *list = gcmp_list_from_path(c, dir, *list, GCMP_TYPE_RFILE);
243 g_completion_add_items(gcmp, *list);
244 *dir_list = g_list_append(*dir_list, g_strdup(dir));
245 }
247 void add_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
248 {
249 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
250 GList **dir_list = tmp->dir_list;
251 GList **list = tmp->list;
252 mpdclient_t *c = tmp->c;
254 D("pre_completion()...\n");
255 if( *list == NULL )
256 {
257 /* create initial list */
258 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_RFILE);
259 g_completion_add_items(gcmp, *list);
260 }
261 else if( line && line[0] && line[strlen(line)-1]=='/' &&
262 string_list_find(*dir_list, line) == NULL )
263 {
264 /* add directory content to list */
265 add_dir(gcmp, line, dir_list, list, c);
266 }
267 }
269 void add_post_completion_cb(GCompletion *gcmp, gchar *line, GList *items,
270 void *data)
271 {
272 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
273 GList **dir_list = tmp->dir_list;
274 GList **list = tmp->list;
275 mpdclient_t *c = tmp->c;
276 screen_t *screen = tmp->screen;
278 D("post_completion()...\n");
279 if( g_list_length(items)>=1 )
280 {
281 screen_display_completion_list(screen, items);
282 lw->clear = 1;
283 lw->repaint = 1;
284 }
286 if( line && line[0] && line[strlen(line)-1]=='/' &&
287 string_list_find(*dir_list, line) == NULL )
288 {
289 /* add directory content to list */
290 add_dir(gcmp, line, dir_list, list, c);
291 }
292 }
294 static int
295 handle_add_to_playlist(screen_t *screen, mpdclient_t *c)
296 {
297 gchar *path;
298 GCompletion *gcmp;
299 GList *list = NULL;
300 GList *dir_list = NULL;
301 completion_callback_data_t data;
303 /* initialize completion support */
304 gcmp = g_completion_new(NULL);
305 g_completion_set_compare(gcmp, strncmp);
306 data.list = &list;
307 data.dir_list = &dir_list;
308 data.screen = screen;
309 data.c = c;
310 wrln_completion_callback_data = &data;
311 wrln_pre_completion_callback = add_pre_completion_cb;
312 wrln_post_completion_callback = add_post_completion_cb;
313 /* get path */
314 path = screen_readln(screen->status_window.w,
315 _("Add: "),
316 NULL,
317 NULL,
318 gcmp);
320 /* destroy completion data */
321 wrln_completion_callback_data = NULL;
322 wrln_pre_completion_callback = NULL;
323 wrln_post_completion_callback = NULL;
324 g_completion_free(gcmp);
325 string_list_free(list);
326 string_list_free(dir_list);
328 /* add the path to the playlist */
329 if( path && path[0] )
330 mpdclient_cmd_add_path(c, path);
332 return 0;
333 }
335 static void
336 play_init(WINDOW *w, int cols, int rows)
337 {
338 lw = list_window_init(w, cols, rows);
339 }
341 static void
342 play_open(screen_t *screen, mpdclient_t *c)
343 {
344 static gboolean install_cb = TRUE;
346 if( install_cb )
347 {
348 mpdclient_install_playlist_callback(c, playlist_changed_callback);
349 install_cb = FALSE;
350 }
351 }
353 static void
354 play_resize(int cols, int rows)
355 {
356 lw->cols = cols;
357 lw->rows = rows;
358 }
361 static void
362 play_exit(void)
363 {
364 list_window_free(lw);
365 }
367 static char *
368 play_title(char *str, size_t size)
369 {
370 if( strcmp(options.host, "localhost") == 0 )
371 return _("Playlist");
373 g_snprintf(str, size, _("Playlist on %s"), options.host);
375 return str;
376 }
378 static void
379 play_paint(screen_t *screen, mpdclient_t *c)
380 {
381 lw->clear = 1;
383 list_window_paint(lw, list_callback, (void *) c);
384 wnoutrefresh(lw->w);
385 }
387 static void
388 play_update(screen_t *screen, mpdclient_t *c)
389 {
390 /* hide the cursor when mpd are playing and the user are inactive */
391 if( options.hide_cursor>0 && c->status->state == MPD_STATUS_STATE_PLAY &&
392 time(NULL)-screen->input_timestamp >= options.hide_cursor )
393 {
394 lw->flags |= LW_HIDE_CURSOR;
395 }
396 else
397 {
398 lw->flags &= ~LW_HIDE_CURSOR;
399 }
401 /* center the cursor */
402 if( options.auto_center )
403 {
404 static int prev_song_id = 0;
406 if( c->song && prev_song_id != c->song->id )
407 {
408 center_playing_item(screen, c);
409 prev_song_id = c->song->id;
410 }
411 }
413 if( c->playlist.updated )
414 {
415 if( lw->selected >= c->playlist.length )
416 lw->selected = c->playlist.length-1;
417 if( lw->start >= c->playlist.length )
418 list_window_reset(lw);
420 play_paint(screen, c);
421 c->playlist.updated = FALSE;
422 }
423 else if( lw->repaint || 1)
424 {
425 list_window_paint(lw, list_callback, (void *) c);
426 wnoutrefresh(lw->w);
427 lw->repaint = 0;
428 }
429 }
431 #ifdef HAVE_GETMOUSE
432 static int
433 handle_mouse_event(screen_t *screen, mpdclient_t *c)
434 {
435 int row;
436 int selected;
437 unsigned long bstate;
439 if( screen_get_mouse_event(c, lw, c->playlist.length, &bstate, &row) )
440 return 1;
442 if( bstate & BUTTON1_DOUBLE_CLICKED )
443 {
444 /* stop */
445 screen_cmd(c, CMD_STOP);
446 return 1;
447 }
449 selected = lw->start+row;
451 if( bstate & BUTTON1_CLICKED )
452 {
453 /* play */
454 if( lw->start+row < c->playlist.length )
455 mpdclient_cmd_play(c, lw->start+row);
456 }
457 else if( bstate & BUTTON3_CLICKED )
458 {
459 /* delete */
460 if( selected == lw->selected )
461 mpdclient_cmd_delete(c, lw->selected);
462 }
463 lw->selected = selected;
464 list_window_check_selected(lw, c->playlist.length);
466 return 1;
467 }
468 #else
469 #define handle_mouse_event(s,c) (0)
470 #endif
472 static int
473 play_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
474 {
475 switch(cmd)
476 {
477 case CMD_PLAY:
478 mpdclient_cmd_play(c, lw->selected);
479 return 1;
480 case CMD_DELETE:
481 mpdclient_cmd_delete(c, lw->selected);
482 return 1;
483 case CMD_SAVE_PLAYLIST:
484 playlist_save(screen, c, NULL, NULL);
485 return 1;
486 case CMD_ADD:
487 handle_add_to_playlist(screen, c);
488 return 1;
489 case CMD_SCREEN_UPDATE:
490 screen->painted = 0;
491 lw->clear = 1;
492 lw->repaint = 1;
493 center_playing_item(screen, c);
494 return 1;
495 case CMD_LIST_MOVE_UP:
496 mpdclient_cmd_move(c, lw->selected, lw->selected-1);
497 return 1;
498 case CMD_LIST_MOVE_DOWN:
499 mpdclient_cmd_move(c, lw->selected, lw->selected+1);
500 return 1;
501 case CMD_LIST_FIND:
502 case CMD_LIST_RFIND:
503 case CMD_LIST_FIND_NEXT:
504 case CMD_LIST_RFIND_NEXT:
505 return screen_find(screen, c,
506 lw, c->playlist.length,
507 cmd, list_callback, (void *) c);
508 case CMD_MOUSE_EVENT:
509 return handle_mouse_event(screen,c);
510 default:
511 break;
512 }
513 return list_window_cmd(lw, c->playlist.length, cmd) ;
514 }
518 static list_window_t *
519 play_lw(void)
520 {
521 return lw;
522 }
525 screen_functions_t *
526 get_screen_playlist(void)
527 {
528 static screen_functions_t functions;
530 memset(&functions, 0, sizeof(screen_functions_t));
531 functions.init = play_init;
532 functions.exit = play_exit;
533 functions.open = play_open;
534 functions.close = NULL;
535 functions.resize = play_resize;
536 functions.paint = play_paint;
537 functions.update = play_update;
538 functions.cmd = play_cmd;
539 functions.get_lw = play_lw;
540 functions.get_title = play_title;
542 return &functions;
543 }