3aaff229dbdaadfbe7f407ef5539679f336bb3da
1 /*
2 * (c) 2004 by Kalle Wallin <kaw@linux.se>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 */
19 #include "config.h"
20 #include "ncmpc.h"
21 #include "options.h"
22 #include "support.h"
23 #include "mpdclient.h"
24 #include "utils.h"
25 #include "strfsong.h"
26 #include "wreadln.h"
27 #include "command.h"
28 #include "colors.h"
29 #include "screen.h"
30 #include "screen_utils.h"
31 #include "screen_play.h"
32 #include "gcc.h"
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 #include <glib.h>
39 #include <ncurses.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 GTime input_timestamp;
52 static list_window_t *lw = NULL;
53 static long long playlist_id;
55 static void
56 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
57 {
58 D("screen_play.c> playlist_callback() [%d]\n", event);
59 switch (event) {
60 case PLAYLIST_EVENT_DELETE:
61 break;
62 case PLAYLIST_EVENT_MOVE:
63 lw->selected = *((int *) data);
64 if (lw->selected < lw->start)
65 lw->start--;
66 break;
67 default:
68 break;
69 }
71 list_window_check_selected(lw, c->playlist.list->len);
72 }
74 static const char *
75 list_callback(unsigned idx, int *highlight, void *data)
76 {
77 static char songname[MAX_SONG_LENGTH];
78 mpdclient_t *c = (mpdclient_t *) data;
79 mpd_Song *song;
81 if (idx >= playlist_length(&c->playlist))
82 return NULL;
84 song = playlist_get(&c->playlist, idx);
86 if (c->song != NULL && song->id == c->song->id &&
87 c->status != NULL && !IS_STOPPED(c->status->state))
88 *highlight = 1;
90 strfsong(songname, MAX_SONG_LENGTH, LIST_FORMAT, song);
91 return songname;
92 }
94 static void
95 center_playing_item(mpdclient_t *c)
96 {
97 unsigned length = c->playlist.list->len;
98 unsigned offset = lw->selected - lw->start;
99 int idx;
101 if (!c->song || length < lw->rows ||
102 c->status == NULL || IS_STOPPED(c->status->state))
103 return;
105 /* try to center the song that are playing */
106 idx = playlist_get_index(c, c->song);
107 D("Autocenter song id:%d pos:%d index:%d\n", c->song->id,c->song->pos,idx);
108 if (idx < 0)
109 return;
111 list_window_center(lw, length, idx);
113 /* make sure the cursor is in the window */
114 lw->selected = lw->start+offset;
115 list_window_check_selected(lw, length);
117 return;
118 }
120 static void
121 save_pre_completion_cb(GCompletion *gcmp, mpd_unused gchar *line, void *data)
122 {
123 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
124 GList **list = tmp->list;
125 mpdclient_t *c = tmp->c;
127 if( *list == NULL ) {
128 /* create completion list */
129 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_PLAYLIST);
130 g_completion_add_items(gcmp, *list);
131 }
132 }
134 static void
135 save_post_completion_cb(mpd_unused GCompletion *gcmp, mpd_unused gchar *line,
136 GList *items, void *data)
137 {
138 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
139 screen_t *screen = tmp->screen;
141 if (g_list_length(items) >= 1)
142 screen_display_completion_list(screen, items);
143 }
145 int
146 playlist_save(screen_t *screen, mpdclient_t *c, char *name, char *defaultname)
147 {
148 gchar *filename;
149 gint error;
150 GCompletion *gcmp;
151 GList *list = NULL;
152 completion_callback_data_t data;
154 if (name == NULL) {
155 /* initialize completion support */
156 gcmp = g_completion_new(NULL);
157 g_completion_set_compare(gcmp, strncmp);
158 data.list = &list;
159 data.dir_list = NULL;
160 data.screen = screen;
161 data.c = c;
162 wrln_completion_callback_data = &data;
163 wrln_pre_completion_callback = save_pre_completion_cb;
164 wrln_post_completion_callback = save_post_completion_cb;
167 /* query the user for a filename */
168 filename = screen_readln(screen->status_window.w,
169 _("Save playlist as: "),
170 defaultname,
171 NULL,
172 gcmp);
174 /* destroy completion support */
175 wrln_completion_callback_data = NULL;
176 wrln_pre_completion_callback = NULL;
177 wrln_post_completion_callback = NULL;
178 g_completion_free(gcmp);
179 list = string_list_free(list);
180 if( filename )
181 filename=g_strstrip(filename);
182 } else
183 filename=g_strdup(name);
185 if (filename == NULL || filename[0] == '\0')
186 return -1;
188 /* send save command to mpd */
189 D("Saving playlist as \'%s \'...\n", filename);
190 if ((error = mpdclient_cmd_save_playlist(c, filename))) {
191 gint code = GET_ACK_ERROR_CODE(error);
193 if (code == MPD_ACK_ERROR_EXIST) {
194 char *buf;
195 int key;
197 buf = g_strdup_printf(_("Replace %s [%s/%s] ? "),
198 filename, YES, NO);
199 key = tolower(screen_getch(screen->status_window.w,
200 buf));
201 g_free(buf);
203 if (key == YES[0]) {
204 if (mpdclient_cmd_delete_playlist(c, filename)) {
205 g_free(filename);
206 return -1;
207 }
209 error = playlist_save(screen, c, filename, NULL);
210 g_free(filename);
211 return error;
212 }
214 screen_status_printf(_("Aborted!"));
215 }
217 g_free(filename);
218 return -1;
219 }
221 /* success */
222 screen_status_printf(_("Saved %s"), filename);
223 g_free(filename);
224 return 0;
225 }
227 static void add_dir(GCompletion *gcmp, gchar *dir, GList **dir_list,
228 GList **list, mpdclient_t *c)
229 {
230 g_completion_remove_items(gcmp, *list);
231 *list = string_list_remove(*list, dir);
232 *list = gcmp_list_from_path(c, dir, *list, GCMP_TYPE_RFILE);
233 g_completion_add_items(gcmp, *list);
234 *dir_list = g_list_append(*dir_list, g_strdup(dir));
235 }
237 static void add_pre_completion_cb(GCompletion *gcmp, gchar *line, void *data)
238 {
239 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
240 GList **dir_list = tmp->dir_list;
241 GList **list = tmp->list;
242 mpdclient_t *c = tmp->c;
244 D("pre_completion()...\n");
245 if (*list == NULL) {
246 /* create initial list */
247 *list = gcmp_list_from_path(c, "", NULL, GCMP_TYPE_RFILE);
248 g_completion_add_items(gcmp, *list);
249 } else if (line && line[0] && line[strlen(line)-1]=='/' &&
250 string_list_find(*dir_list, line) == NULL) {
251 /* add directory content to list */
252 add_dir(gcmp, line, dir_list, list, c);
253 }
254 }
256 static void add_post_completion_cb(GCompletion *gcmp, gchar *line,
257 GList *items, void *data)
258 {
259 completion_callback_data_t *tmp = (completion_callback_data_t *)data;
260 GList **dir_list = tmp->dir_list;
261 GList **list = tmp->list;
262 mpdclient_t *c = tmp->c;
263 screen_t *screen = tmp->screen;
265 D("post_completion()...\n");
266 if (g_list_length(items) >= 1)
267 screen_display_completion_list(screen, items);
269 if (line && line[0] && line[strlen(line) - 1] == '/' &&
270 string_list_find(*dir_list, line) == NULL) {
271 /* add directory content to list */
272 add_dir(gcmp, line, dir_list, list, c);
273 }
274 }
276 static int
277 handle_add_to_playlist(screen_t *screen, mpdclient_t *c)
278 {
279 gchar *path;
280 GCompletion *gcmp;
281 GList *list = NULL;
282 GList *dir_list = NULL;
283 completion_callback_data_t data;
285 /* initialize completion support */
286 gcmp = g_completion_new(NULL);
287 g_completion_set_compare(gcmp, strncmp);
288 data.list = &list;
289 data.dir_list = &dir_list;
290 data.screen = screen;
291 data.c = c;
292 wrln_completion_callback_data = &data;
293 wrln_pre_completion_callback = add_pre_completion_cb;
294 wrln_post_completion_callback = add_post_completion_cb;
296 /* get path */
297 path = screen_readln(screen->status_window.w,
298 _("Add: "),
299 NULL,
300 NULL,
301 gcmp);
303 /* destroy completion data */
304 wrln_completion_callback_data = NULL;
305 wrln_pre_completion_callback = NULL;
306 wrln_post_completion_callback = NULL;
307 g_completion_free(gcmp);
308 string_list_free(list);
309 string_list_free(dir_list);
311 /* add the path to the playlist */
312 if (path && path[0])
313 mpdclient_cmd_add_path(c, path);
315 return 0;
316 }
318 static void
319 play_init(WINDOW *w, int cols, int rows)
320 {
321 lw = list_window_init(w, cols, rows);
322 }
324 static void
325 play_open(mpd_unused screen_t *screen, mpdclient_t *c)
326 {
327 static gboolean install_cb = TRUE;
329 input_timestamp = time(NULL);
331 if (install_cb) {
332 mpdclient_install_playlist_callback(c, playlist_changed_callback);
333 install_cb = FALSE;
334 }
335 }
337 static void
338 play_resize(int cols, int rows)
339 {
340 lw->cols = cols;
341 lw->rows = rows;
342 }
345 static void
346 play_exit(void)
347 {
348 list_window_free(lw);
349 }
351 static const char *
352 play_title(char *str, size_t size)
353 {
354 if( strcmp(options.host, "localhost") == 0 )
355 return _("Playlist");
357 g_snprintf(str, size, _("Playlist on %s"), options.host);
358 return str;
359 }
361 static void
362 play_paint(mpdclient_t *c)
363 {
364 list_window_paint(lw, list_callback, (void *) c);
365 }
367 static void
368 play_update(mpd_unused screen_t *screen, mpdclient_t *c)
369 {
370 /* hide the cursor when mpd are playing and the user are inactive */
371 if (options.hide_cursor > 0 &&
372 (c->status != NULL && c->status->state == MPD_STATUS_STATE_PLAY) &&
373 time(NULL) - input_timestamp >= options.hide_cursor ) {
374 lw->flags |= LW_HIDE_CURSOR;
375 } else {
376 lw->flags &= ~LW_HIDE_CURSOR;
377 }
379 /* center the cursor */
380 if (options.auto_center) {
381 static int prev_song_id = 0;
383 if (c->song && prev_song_id != c->song->id) {
384 center_playing_item(c);
385 prev_song_id = c->song->id;
386 }
387 }
389 if (c->playlist.id != playlist_id) {
390 list_window_check_selected(lw, playlist_length(&c->playlist));
391 play_paint(c);
392 playlist_id = c->playlist.id;
393 } else {
394 list_window_paint(lw, list_callback, (void *) c);
395 }
396 }
398 #ifdef HAVE_GETMOUSE
399 static int
400 handle_mouse_event(mpd_unused screen_t *screen, mpdclient_t *c)
401 {
402 int row;
403 unsigned selected;
404 unsigned long bstate;
406 if (screen_get_mouse_event(c, &bstate, &row) ||
407 list_window_mouse(lw, c->playlist.list->len, bstate, row))
408 return 1;
410 if (bstate & BUTTON1_DOUBLE_CLICKED) {
411 /* stop */
412 screen_cmd(c, CMD_STOP);
413 return 1;
414 }
416 selected = lw->start + row;
418 if (bstate & BUTTON1_CLICKED) {
419 /* play */
420 if (lw->start + row < c->playlist.list->len)
421 mpdclient_cmd_play(c, lw->start + row);
422 } else if (bstate & BUTTON3_CLICKED) {
423 /* delete */
424 if (selected == lw->selected)
425 mpdclient_cmd_delete(c, lw->selected);
426 }
428 lw->selected = selected;
429 list_window_check_selected(lw, c->playlist.list->len);
431 return 1;
432 }
433 #else
434 #define handle_mouse_event(s,c) (0)
435 #endif
437 static int
438 play_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
439 {
440 input_timestamp = time(NULL);
442 switch(cmd) {
443 case CMD_PLAY:
444 mpdclient_cmd_play(c, lw->selected);
445 return 1;
446 case CMD_DELETE:
447 mpdclient_cmd_delete(c, lw->selected);
448 return 1;
449 case CMD_SAVE_PLAYLIST:
450 playlist_save(screen, c, NULL, NULL);
451 return 1;
452 case CMD_ADD:
453 handle_add_to_playlist(screen, c);
454 return 1;
455 case CMD_SCREEN_UPDATE:
456 center_playing_item(c);
457 return 0;
459 case CMD_LIST_MOVE_UP:
460 mpdclient_cmd_move(c, lw->selected, lw->selected-1);
461 return 1;
462 case CMD_LIST_MOVE_DOWN:
463 mpdclient_cmd_move(c, lw->selected, lw->selected+1);
464 return 1;
465 case CMD_LIST_FIND:
466 case CMD_LIST_RFIND:
467 case CMD_LIST_FIND_NEXT:
468 case CMD_LIST_RFIND_NEXT:
469 return screen_find(screen,
470 lw, c->playlist.list->len,
471 cmd, list_callback, (void *) c);
472 case CMD_MOUSE_EVENT:
473 return handle_mouse_event(screen,c);
474 default:
475 break;
476 }
477 return list_window_cmd(lw, c->playlist.list->len, cmd);
478 }
480 const struct screen_functions screen_playlist = {
481 .init = play_init,
482 .exit = play_exit,
483 .open = play_open,
484 .close = NULL,
485 .resize = play_resize,
486 .paint = play_paint,
487 .update = play_update,
488 .cmd = play_cmd,
489 .get_title = play_title,
490 };