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 "config.h"
22 #include "ncmpc.h"
23 #include "options.h"
24 #include "support.h"
25 #include "mpdclient.h"
26 #include "strfsong.h"
27 #include "command.h"
28 #include "screen.h"
29 #include "screen_utils.h"
30 #include "screen_browse.h"
31 #include "screen_play.h"
32 #include "gcc.h"
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <glib.h>
38 #include <ncurses.h>
40 #define USE_OLD_LAYOUT
41 #undef USE_OLD_ADD
43 #define BUFSIZE 1024
45 #define HIGHLIGHT (0x01)
48 static list_window_t *lw = NULL;
49 static list_window_state_t *lw_state = NULL;
50 static mpdclient_filelist_t *filelist = NULL;
53 /* clear the highlight flag for all items in the filelist */
54 void
55 clear_highlights(mpdclient_filelist_t *fl)
56 {
57 GList *list = g_list_first(fl->list);
59 while( list ) {
60 filelist_entry_t *entry = list->data;
62 entry->flags &= ~HIGHLIGHT;
63 list = list->next;
64 }
65 }
67 /* change the highlight flag for a song */
68 void
69 set_highlight(mpdclient_filelist_t *fl, mpd_Song *song, int highlight)
70 {
71 GList *list = g_list_first(fl->list);
73 if( !song )
74 return;
76 while( list ) {
77 filelist_entry_t *entry = list->data;
78 mpd_InfoEntity *entity = entry->entity;
80 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
81 mpd_Song *song2 = entity->info.song;
83 if( strcmp(song->file, song2->file) == 0 ) {
84 if(highlight)
85 entry->flags |= HIGHLIGHT;
86 else
87 entry->flags &= ~HIGHLIGHT;
88 }
89 }
90 list = list->next;
91 }
92 }
94 /* sync highlight flags with playlist */
95 void
96 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *fl)
97 {
98 GList *list = g_list_first(fl->list);
100 while(list) {
101 filelist_entry_t *entry = list->data;
102 mpd_InfoEntity *entity = entry->entity;
104 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
105 mpd_Song *song = entity->info.song;
107 if( playlist_get_index_from_file(c, song->file) >= 0 )
108 entry->flags |= HIGHLIGHT;
109 else
110 entry->flags &= ~HIGHLIGHT;
111 }
112 list=list->next;
113 }
114 }
116 /* the db have changed -> update the filelist */
117 static void
118 file_changed_callback(mpdclient_t *c, mpd_unused int event,
119 mpd_unused gpointer data)
120 {
121 D("screen_file.c> filelist_callback() [%d]\n", event);
122 filelist = mpdclient_filelist_update(c, filelist);
123 sync_highlights(c, filelist);
124 list_window_check_selected(lw, filelist->length);
125 }
127 /* the playlist have been updated -> fix highlights */
128 static void
129 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
130 {
131 D("screen_file.c> playlist_callback() [%d]\n", event);
132 switch(event) {
133 case PLAYLIST_EVENT_CLEAR:
134 clear_highlights(filelist);
135 break;
136 case PLAYLIST_EVENT_ADD:
137 set_highlight(filelist, (mpd_Song *) data, 1);
138 break;
139 case PLAYLIST_EVENT_DELETE:
140 set_highlight(filelist, (mpd_Song *) data, 0);
141 break;
142 case PLAYLIST_EVENT_MOVE:
143 break;
144 default:
145 sync_highlights(c, filelist);
146 break;
147 }
148 }
150 /* list_window callback */
151 const char *
152 browse_lw_callback(unsigned idx, int *highlight, void *data)
153 {
154 static char buf[BUFSIZE];
155 mpdclient_filelist_t *fl = (mpdclient_filelist_t *) data;
156 filelist_entry_t *entry;
157 mpd_InfoEntity *entity;
159 *highlight = 0;
160 if( (entry=(filelist_entry_t *)g_list_nth_data(fl->list,idx))==NULL )
161 return NULL;
163 entity = entry->entity;
164 *highlight = (entry->flags & HIGHLIGHT);
166 if( entity == NULL )
167 return "[..]";
169 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
170 mpd_Directory *dir = entity->info.directory;
171 char *directory = utf8_to_locale(basename(dir->path));
173 g_snprintf(buf, BUFSIZE, "[%s]", directory);
174 g_free(directory);
175 return buf;
176 } else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
177 mpd_Song *song = entity->info.song;
179 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
180 return buf;
181 } else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
182 mpd_PlaylistFile *plf = entity->info.playlistFile;
183 char *filename = utf8_to_locale(basename(plf->path));
185 #ifdef USE_OLD_LAYOUT
186 g_snprintf(buf, BUFSIZE, "*%s*", filename);
187 #else
188 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
189 #endif
190 g_free(filename);
191 return buf;
192 }
194 return "Error: Unknown entry!";
195 }
197 /* chdir */
198 static int
199 change_directory(mpd_unused screen_t *screen, mpdclient_t *c,
200 filelist_entry_t *entry, const char *new_path)
201 {
202 mpd_InfoEntity *entity = NULL;
203 gchar *path = NULL;
205 if( entry!=NULL )
206 entity = entry->entity;
207 else if( new_path==NULL )
208 return -1;
210 if( entity==NULL ) {
211 if( entry || 0==strcmp(new_path, "..") ) {
212 /* return to parent */
213 char *parent = g_path_get_dirname(filelist->path);
214 if( strcmp(parent, ".") == 0 )
215 parent[0] = '\0';
216 path = g_strdup(parent);
217 list_window_reset(lw);
218 /* restore previous list window state */
219 list_window_pop_state(lw_state,lw);
220 } else {
221 /* entry==NULL, then new_path ("" is root) */
222 path = g_strdup(new_path);
223 list_window_reset(lw);
224 /* restore first list window state (pop while returning true) */
225 while(list_window_pop_state(lw_state,lw));
226 }
227 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
228 /* enter sub */
229 mpd_Directory *dir = entity->info.directory;
230 path = utf8_to_locale(dir->path);
231 /* save current list window state */
232 list_window_push_state(lw_state,lw);
233 } else
234 return -1;
236 filelist = mpdclient_filelist_free(filelist);
237 filelist = mpdclient_filelist_get(c, path);
238 sync_highlights(c, filelist);
239 list_window_check_selected(lw, filelist->length);
240 g_free(path);
241 return 0;
242 }
244 static int
245 load_playlist(mpd_unused screen_t *screen, mpdclient_t *c,
246 filelist_entry_t *entry)
247 {
248 mpd_InfoEntity *entity = entry->entity;
249 mpd_PlaylistFile *plf = entity->info.playlistFile;
250 char *filename = utf8_to_locale(plf->path);
252 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
253 screen_status_printf(_("Loading playlist %s..."), basename(filename));
254 g_free(filename);
255 return 0;
256 }
258 static int
259 handle_save(screen_t *screen, mpdclient_t *c)
260 {
261 filelist_entry_t *entry;
262 char *defaultname = NULL;
265 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
266 if( entry && entry->entity ) {
267 mpd_InfoEntity *entity = entry->entity;
268 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
269 mpd_PlaylistFile *plf = entity->info.playlistFile;
270 defaultname = plf->path;
271 }
272 }
274 return playlist_save(screen, c, NULL, defaultname);
275 }
277 static int
278 handle_delete(screen_t *screen, mpdclient_t *c)
279 {
280 filelist_entry_t *entry;
281 mpd_InfoEntity *entity;
282 mpd_PlaylistFile *plf;
283 char *str, *buf;
284 int key;
286 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
287 if( entry==NULL || entry->entity==NULL )
288 return -1;
290 entity = entry->entity;
292 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
293 screen_status_printf(_("You can only delete playlists!"));
294 screen_bell();
295 return -1;
296 }
298 plf = entity->info.playlistFile;
299 str = utf8_to_locale(basename(plf->path));
300 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
301 g_free(str);
302 key = tolower(screen_getch(screen->status_window.w, buf));
303 g_free(buf);
304 if( key==KEY_RESIZE )
305 screen_resize();
306 if( key != YES[0] ) {
307 screen_status_printf(_("Aborted!"));
308 return 0;
309 }
311 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
312 return -1;
314 screen_status_printf(_("Playlist deleted!"));
315 return 0;
316 }
318 static int
319 enqueue_and_play(mpd_unused screen_t *screen, mpdclient_t *c,
320 filelist_entry_t *entry)
321 {
322 int idx;
323 mpd_InfoEntity *entity = entry->entity;
324 mpd_Song *song = entity->info.song;
326 if(!( entry->flags & HIGHLIGHT )) {
327 if( mpdclient_cmd_add(c, song) == 0 ) {
328 char buf[BUFSIZE];
330 entry->flags |= HIGHLIGHT;
331 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
332 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
333 mpdclient_update(c); /* get song id */
334 } else
335 return -1;
336 }
338 idx = playlist_get_index_from_file(c, song->file);
339 mpdclient_cmd_play(c, idx);
340 return 0;
341 }
343 int
344 browse_handle_enter(screen_t *screen,
345 mpdclient_t *c,
346 list_window_t *local_lw,
347 mpdclient_filelist_t *fl)
348 {
349 filelist_entry_t *entry;
350 mpd_InfoEntity *entity;
352 if ( fl==NULL )
353 return -1;
354 entry = ( filelist_entry_t *) g_list_nth_data(fl->list, local_lw->selected);
355 if( entry==NULL )
356 return -1;
358 entity = entry->entity;
359 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
360 return change_directory(screen, c, entry, NULL);
361 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
362 return load_playlist(screen, c, entry);
363 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
364 return enqueue_and_play(screen, c, entry);
365 return -1;
366 }
369 #ifdef USE_OLD_ADD
370 /* NOTE - The add_directory functions should move to mpdclient.c */
371 extern gint mpdclient_finish_command(mpdclient_t *c);
373 static int
374 add_directory(mpdclient_t *c, char *dir)
375 {
376 mpd_InfoEntity *entity;
377 GList *subdir_list = NULL;
378 GList *list = NULL;
379 char *dirname;
381 dirname = utf8_to_locale(dir);
382 screen_status_printf(_("Adding directory %s...\n"), dirname);
383 doupdate();
384 g_free(dirname);
385 dirname = NULL;
387 mpd_sendLsInfoCommand(c->connection, dir);
388 mpd_sendCommandListBegin(c->connection);
389 while( (entity=mpd_getNextInfoEntity(c->connection)) ) {
390 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
391 mpd_Song *song = entity->info.song;
392 mpd_sendAddCommand(c->connection, song->file);
393 mpd_freeInfoEntity(entity);
394 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
395 subdir_list = g_list_append(subdir_list, (gpointer) entity);
396 } else
397 mpd_freeInfoEntity(entity);
398 }
399 mpd_sendCommandListEnd(c->connection);
400 mpdclient_finish_command(c);
401 c->need_update = TRUE;
403 list = g_list_first(subdir_list);
404 while( list!=NULL ) {
405 mpd_Directory *dir;
407 entity = list->data;
408 dir = entity->info.directory;
409 add_directory(c, dir->path);
410 mpd_freeInfoEntity(entity);
411 list->data=NULL;
412 list=list->next;
413 }
414 g_list_free(subdir_list);
415 return 0;
416 }
417 #endif
419 int
420 browse_handle_select(screen_t *screen,
421 mpdclient_t *c,
422 list_window_t *local_lw,
423 mpdclient_filelist_t *fl)
424 {
425 filelist_entry_t *entry;
427 if ( fl==NULL )
428 return -1;
429 entry=( filelist_entry_t *) g_list_nth_data(fl->list,
430 local_lw->selected);
431 if( entry==NULL || entry->entity==NULL)
432 return -1;
434 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
435 return load_playlist(screen, c, entry);
437 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
438 mpd_Directory *dir = entry->entity->info.directory;
439 #ifdef USE_OLD_ADD
440 add_directory(c, tmp);
441 #else
442 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 ) {
443 char *tmp = utf8_to_locale(dir->path);
445 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
446 g_free(tmp);
447 }
448 #endif
449 return 0;
450 }
452 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
453 return -1;
455 if( entry->flags & HIGHLIGHT )
456 entry->flags &= ~HIGHLIGHT;
457 else
458 entry->flags |= HIGHLIGHT;
460 if( entry->flags & HIGHLIGHT ) {
461 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
462 mpd_Song *song = entry->entity->info.song;
464 if( mpdclient_cmd_add(c, song) == 0 ) {
465 char buf[BUFSIZE];
467 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
468 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
469 }
470 }
471 } else {
472 /* remove song from playlist */
473 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
474 mpd_Song *song = entry->entity->info.song;
476 if( song ) {
477 int idx = playlist_get_index_from_file(c, song->file);
479 while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
480 mpdclient_cmd_delete(c, idx);
481 }
482 }
483 }
484 return 0;
485 }
487 int
488 browse_handle_select_all (screen_t *screen,
489 mpdclient_t *c,
490 mpd_unused list_window_t *local_lw,
491 mpdclient_filelist_t *fl)
492 {
493 filelist_entry_t *entry;
494 GList *temp = fl->list;
496 if ( fl==NULL )
497 return -1;
498 for (fl->list = g_list_first(fl->list);
499 fl->list;
500 fl->list = g_list_next(fl->list)) {
501 entry=( filelist_entry_t *) fl->list->data;
502 if( entry==NULL || entry->entity==NULL)
503 return -1;
505 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
506 load_playlist(screen, c, entry);
508 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
509 mpd_Directory *dir = entry->entity->info.directory;
510 #ifdef USE_OLD_ADD
511 add_directory(c, tmp);
512 #else
513 if (mpdclient_cmd_add_path_utf8(c, dir->path) == 0) {
514 char *tmp = utf8_to_locale(dir->path);
516 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
517 g_free(tmp);
518 }
519 #endif
520 }
522 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
523 continue;
525 entry->flags |= HIGHLIGHT;
527 if( entry->flags & HIGHLIGHT ) {
528 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
529 mpd_Song *song = entry->entity->info.song;
531 if( mpdclient_cmd_add(c, song) == 0 ) {
532 char buf[BUFSIZE];
534 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
535 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
536 }
537 }
538 }
539 /*
540 else {
541 //remove song from playlist
542 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
543 mpd_Song *song = entry->entity->info.song;
545 if( song ) {
546 int idx = playlist_get_index_from_file(c, song->file);
548 while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
549 mpdclient_cmd_delete(c, idx);
550 }
551 }
552 }
553 */
554 return 0;
555 }
557 fl->list = temp;
558 return 0;
559 }
561 static void
562 browse_init(WINDOW *w, int cols, int rows)
563 {
564 lw = list_window_init(w, cols, rows);
565 lw_state = list_window_init_state();
566 }
568 static void
569 browse_resize(int cols, int rows)
570 {
571 lw->cols = cols;
572 lw->rows = rows;
573 }
575 static void
576 browse_exit(void)
577 {
578 if( filelist )
579 filelist = mpdclient_filelist_free(filelist);
580 lw = list_window_free(lw);
581 lw_state = list_window_free_state(lw_state);
582 }
584 static void
585 browse_open(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
586 {
587 if( filelist == NULL ) {
588 filelist = mpdclient_filelist_get(c, "");
589 mpdclient_install_playlist_callback(c, playlist_changed_callback);
590 mpdclient_install_browse_callback(c, file_changed_callback);
591 }
592 }
594 static void
595 browse_close(void)
596 {
597 }
599 static const char *
600 browse_title(char *str, size_t size)
601 {
602 char *pathcopy;
603 char *parentdir;
605 pathcopy = strdup(filelist->path);
606 parentdir = dirname(pathcopy);
607 parentdir = basename(parentdir);
609 if( parentdir[0] == '.' && strlen(parentdir) == 1 ) {
610 parentdir = NULL;
611 }
613 g_snprintf(str, size, _("Browse: %s%s%s"),
614 parentdir ? parentdir : "",
615 parentdir ? "/" : "",
616 basename(filelist->path));
617 free(pathcopy);
618 return str;
619 }
621 static void
622 browse_paint(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
623 {
624 lw->clear = 1;
626 list_window_paint(lw, browse_lw_callback, (void *) filelist);
627 wnoutrefresh(lw->w);
628 }
630 static void
631 browse_update(screen_t *screen, mpdclient_t *c)
632 {
633 if( filelist->updated ) {
634 browse_paint(screen, c);
635 filelist->updated = FALSE;
636 return;
637 }
639 list_window_paint(lw, browse_lw_callback, (void *) filelist);
640 wnoutrefresh(lw->w);
641 }
644 #ifdef HAVE_GETMOUSE
645 int
646 browse_handle_mouse_event(screen_t *screen,
647 mpdclient_t *c,
648 list_window_t *local_lw,
649 mpdclient_filelist_t *fl)
650 {
651 int row;
652 unsigned prev_selected = local_lw->selected;
653 unsigned long bstate;
654 int length;
656 if ( fl )
657 length = fl->length;
658 else
659 length = 0;
661 if( screen_get_mouse_event(c, local_lw, length, &bstate, &row) )
662 return 1;
664 local_lw->selected = local_lw->start+row;
665 list_window_check_selected(local_lw, length);
667 if( bstate & BUTTON1_CLICKED ) {
668 if( prev_selected == local_lw->selected )
669 browse_handle_enter(screen, c, local_lw, fl);
670 } else if( bstate & BUTTON3_CLICKED ) {
671 if( prev_selected == local_lw->selected )
672 browse_handle_select(screen, c, local_lw, fl);
673 }
675 return 1;
676 }
677 #endif
679 static int
680 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
681 {
682 switch(cmd)
683 {
684 case CMD_PLAY:
685 browse_handle_enter(screen, c, lw, filelist);
686 return 1;
687 case CMD_GO_ROOT_DIRECTORY:
688 return change_directory(screen, c, NULL, "");
689 break;
690 case CMD_GO_PARENT_DIRECTORY:
691 return change_directory(screen, c, NULL, "..");
692 break;
693 case CMD_SELECT:
694 if( browse_handle_select(screen, c, lw, filelist) == 0 )
695 {
696 /* continue and select next item... */
697 cmd = CMD_LIST_NEXT;
698 }
699 break;
700 case CMD_DELETE:
701 handle_delete(screen, c);
702 break;
703 case CMD_SAVE_PLAYLIST:
704 handle_save(screen, c);
705 break;
706 case CMD_SCREEN_UPDATE:
707 screen->painted = 0;
708 lw->clear = 1;
709 lw->repaint = 1;
710 filelist = mpdclient_filelist_update(c, filelist);
711 list_window_check_selected(lw, filelist->length);
712 screen_status_printf(_("Screen updated!"));
713 return 1;
714 case CMD_DB_UPDATE:
715 if( !c->status->updatingDb )
716 {
717 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
718 {
719 if(strcmp(filelist->path,"")) {
720 screen_status_printf(_("Database update of %s started!"),
721 filelist->path);
722 } else {
723 screen_status_printf(_("Database update started!"));
724 }
725 /* set updatingDb to make shure the browse callback gets called
726 * even if the updated has finished before status is updated */
727 c->status->updatingDb = 1;
728 }
729 }
730 else
731 screen_status_printf(_("Database update running..."));
732 return 1;
733 case CMD_LIST_FIND:
734 case CMD_LIST_RFIND:
735 case CMD_LIST_FIND_NEXT:
736 case CMD_LIST_RFIND_NEXT:
737 return screen_find(screen,
738 lw, filelist->length,
739 cmd, browse_lw_callback, (void *) filelist);
740 case CMD_MOUSE_EVENT:
741 return browse_handle_mouse_event(screen,c,lw,filelist);
742 default:
743 break;
744 }
745 return list_window_cmd(lw, filelist->length, cmd);
746 }
748 static list_window_t *
749 get_filelist_window(void)
750 {
751 return lw;
752 }
754 const struct screen_functions screen_browse = {
755 .init = browse_init,
756 .exit = browse_exit,
757 .open = browse_open,
758 .close = browse_close,
759 .resize = browse_resize,
760 .paint = browse_paint,
761 .update = browse_update,
762 .cmd = browse_cmd,
763 .get_lw = get_filelist_window,
764 .get_title = browse_title,
765 };