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"
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <glib.h>
37 #include <ncurses.h>
39 #define USE_OLD_LAYOUT
40 #undef USE_OLD_ADD
42 #define BUFSIZE 1024
44 #define HIGHLIGHT (0x01)
47 static list_window_t *lw = NULL;
48 static list_window_state_t *lw_state = NULL;
49 static mpdclient_filelist_t *filelist = NULL;
52 /* clear the highlight flag for all items in the filelist */
53 void
54 clear_highlights(mpdclient_filelist_t *filelist)
55 {
56 GList *list = g_list_first(filelist->list);
58 while( list ) {
59 filelist_entry_t *entry = list->data;
61 entry->flags &= ~HIGHLIGHT;
62 list = list->next;
63 }
64 }
66 /* change the highlight flag for a song */
67 void
68 set_highlight(mpdclient_filelist_t *filelist, mpd_Song *song, int highlight)
69 {
70 GList *list = g_list_first(filelist->list);
72 if( !song )
73 return;
75 while( list ) {
76 filelist_entry_t *entry = list->data;
77 mpd_InfoEntity *entity = entry->entity;
79 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
80 mpd_Song *song2 = entity->info.song;
82 if( strcmp(song->file, song2->file) == 0 ) {
83 if(highlight)
84 entry->flags |= HIGHLIGHT;
85 else
86 entry->flags &= ~HIGHLIGHT;
87 }
88 }
89 list = list->next;
90 }
91 }
93 /* sync highlight flags with playlist */
94 void
95 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *filelist)
96 {
97 GList *list = g_list_first(filelist->list);
99 while(list) {
100 filelist_entry_t *entry = list->data;
101 mpd_InfoEntity *entity = entry->entity;
103 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
104 mpd_Song *song = entity->info.song;
106 if( playlist_get_index_from_file(c, song->file) >= 0 )
107 entry->flags |= HIGHLIGHT;
108 else
109 entry->flags &= ~HIGHLIGHT;
110 }
111 list=list->next;
112 }
113 }
115 /* the db have changed -> update the filelist */
116 static void
117 file_changed_callback(mpdclient_t *c, int event, gpointer data)
118 {
119 D("screen_file.c> filelist_callback() [%d]\n", event);
120 filelist = mpdclient_filelist_update(c, filelist);
121 sync_highlights(c, filelist);
122 list_window_check_selected(lw, filelist->length);
123 }
125 /* the playlist have been updated -> fix highlights */
126 static void
127 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
128 {
129 D("screen_file.c> playlist_callback() [%d]\n", event);
130 switch(event) {
131 case PLAYLIST_EVENT_CLEAR:
132 clear_highlights(filelist);
133 break;
134 case PLAYLIST_EVENT_ADD:
135 set_highlight(filelist, (mpd_Song *) data, 1);
136 break;
137 case PLAYLIST_EVENT_DELETE:
138 set_highlight(filelist, (mpd_Song *) data, 0);
139 break;
140 case PLAYLIST_EVENT_MOVE:
141 break;
142 default:
143 sync_highlights(c, filelist);
144 break;
145 }
146 }
148 /* list_window callback */
149 const char *
150 browse_lw_callback(int index, int *highlight, void *data)
151 {
152 static char buf[BUFSIZE];
153 mpdclient_filelist_t *filelist = (mpdclient_filelist_t *) data;
154 filelist_entry_t *entry;
155 mpd_InfoEntity *entity;
157 *highlight = 0;
158 if( (entry=(filelist_entry_t *)g_list_nth_data(filelist->list,index))==NULL )
159 return NULL;
161 entity = entry->entity;
162 *highlight = (entry->flags & HIGHLIGHT);
164 if( entity == NULL )
165 return "[..]";
167 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
168 mpd_Directory *dir = entity->info.directory;
169 char *dirname = utf8_to_locale(basename(dir->path));
171 g_snprintf(buf, BUFSIZE, "[%s]", dirname);
172 g_free(dirname);
173 return buf;
174 } else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
175 mpd_Song *song = entity->info.song;
177 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
178 return buf;
179 } else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
180 mpd_PlaylistFile *plf = entity->info.playlistFile;
181 char *filename = utf8_to_locale(basename(plf->path));
183 #ifdef USE_OLD_LAYOUT
184 g_snprintf(buf, BUFSIZE, "*%s*", filename);
185 #else
186 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
187 #endif
188 g_free(filename);
189 return buf;
190 }
192 return "Error: Unknown entry!";
193 }
195 /* chdir */
196 static int
197 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry,
198 const char *new_path)
199 {
200 mpd_InfoEntity *entity = NULL;
201 gchar *path = NULL;
203 if( entry!=NULL )
204 entity = entry->entity;
205 else if( new_path==NULL )
206 return -1;
208 if( entity==NULL ) {
209 if( entry || 0==strcmp(new_path, "..") ) {
210 /* return to parent */
211 char *parent = g_path_get_dirname(filelist->path);
212 if( strcmp(parent, ".") == 0 )
213 parent[0] = '\0';
214 path = g_strdup(parent);
215 list_window_reset(lw);
216 /* restore previous list window state */
217 list_window_pop_state(lw_state,lw);
218 } else {
219 /* entry==NULL, then new_path ("" is root) */
220 path = g_strdup(new_path);
221 list_window_reset(lw);
222 /* restore first list window state (pop while returning true) */
223 while(list_window_pop_state(lw_state,lw));
224 }
225 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
226 /* enter sub */
227 mpd_Directory *dir = entity->info.directory;
228 path = utf8_to_locale(dir->path);
229 /* save current list window state */
230 list_window_push_state(lw_state,lw);
231 } else
232 return -1;
234 filelist = mpdclient_filelist_free(filelist);
235 filelist = mpdclient_filelist_get(c, path);
236 sync_highlights(c, filelist);
237 list_window_check_selected(lw, filelist->length);
238 g_free(path);
239 return 0;
240 }
242 static int
243 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
244 {
245 mpd_InfoEntity *entity = entry->entity;
246 mpd_PlaylistFile *plf = entity->info.playlistFile;
247 char *filename = utf8_to_locale(plf->path);
249 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
250 screen_status_printf(_("Loading playlist %s..."), basename(filename));
251 g_free(filename);
252 return 0;
253 }
255 static int
256 handle_save(screen_t *screen, mpdclient_t *c)
257 {
258 filelist_entry_t *entry;
259 char *defaultname = NULL;
262 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
263 if( entry && entry->entity ) {
264 mpd_InfoEntity *entity = entry->entity;
265 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
266 mpd_PlaylistFile *plf = entity->info.playlistFile;
267 defaultname = plf->path;
268 }
269 }
271 return playlist_save(screen, c, NULL, defaultname);
272 }
274 static int
275 handle_delete(screen_t *screen, mpdclient_t *c)
276 {
277 filelist_entry_t *entry;
278 mpd_InfoEntity *entity;
279 mpd_PlaylistFile *plf;
280 char *str, *buf;
281 int key;
283 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
284 if( entry==NULL || entry->entity==NULL )
285 return -1;
287 entity = entry->entity;
289 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
290 screen_status_printf(_("You can only delete playlists!"));
291 screen_bell();
292 return -1;
293 }
295 plf = entity->info.playlistFile;
296 str = utf8_to_locale(basename(plf->path));
297 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
298 g_free(str);
299 key = tolower(screen_getch(screen->status_window.w, buf));
300 g_free(buf);
301 if( key==KEY_RESIZE )
302 screen_resize();
303 if( key != YES[0] ) {
304 screen_status_printf(_("Aborted!"));
305 return 0;
306 }
308 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
309 return -1;
311 screen_status_printf(_("Playlist deleted!"));
312 return 0;
313 }
315 static int
316 enqueue_and_play(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
317 {
318 int index;
319 mpd_InfoEntity *entity = entry->entity;
320 mpd_Song *song = entity->info.song;
322 if(!( entry->flags & HIGHLIGHT )) {
323 if( mpdclient_cmd_add(c, song) == 0 ) {
324 char buf[BUFSIZE];
326 entry->flags |= HIGHLIGHT;
327 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
328 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
329 mpdclient_update(c); /* get song id */
330 } else
331 return -1;
332 }
334 index = playlist_get_index_from_file(c, song->file);
335 mpdclient_cmd_play(c, index);
336 return 0;
337 }
339 int
340 browse_handle_enter(screen_t *screen,
341 mpdclient_t *c,
342 list_window_t *lw,
343 mpdclient_filelist_t *filelist)
344 {
345 filelist_entry_t *entry;
346 mpd_InfoEntity *entity;
348 if ( filelist==NULL )
349 return -1;
350 entry = ( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
351 if( entry==NULL )
352 return -1;
354 entity = entry->entity;
355 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
356 return change_directory(screen, c, entry, NULL);
357 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
358 return load_playlist(screen, c, entry);
359 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
360 return enqueue_and_play(screen, c, entry);
361 return -1;
362 }
365 #ifdef USE_OLD_ADD
366 /* NOTE - The add_directory functions should move to mpdclient.c */
367 extern gint mpdclient_finish_command(mpdclient_t *c);
369 static int
370 add_directory(mpdclient_t *c, char *dir)
371 {
372 mpd_InfoEntity *entity;
373 GList *subdir_list = NULL;
374 GList *list = NULL;
375 char *dirname;
377 dirname = utf8_to_locale(dir);
378 screen_status_printf(_("Adding directory %s...\n"), dirname);
379 doupdate();
380 g_free(dirname);
381 dirname = NULL;
383 mpd_sendLsInfoCommand(c->connection, dir);
384 mpd_sendCommandListBegin(c->connection);
385 while( (entity=mpd_getNextInfoEntity(c->connection)) ) {
386 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
387 mpd_Song *song = entity->info.song;
388 mpd_sendAddCommand(c->connection, song->file);
389 mpd_freeInfoEntity(entity);
390 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
391 subdir_list = g_list_append(subdir_list, (gpointer) entity);
392 } else
393 mpd_freeInfoEntity(entity);
394 }
395 mpd_sendCommandListEnd(c->connection);
396 mpdclient_finish_command(c);
397 c->need_update = TRUE;
399 list = g_list_first(subdir_list);
400 while( list!=NULL ) {
401 mpd_Directory *dir;
403 entity = list->data;
404 dir = entity->info.directory;
405 add_directory(c, dir->path);
406 mpd_freeInfoEntity(entity);
407 list->data=NULL;
408 list=list->next;
409 }
410 g_list_free(subdir_list);
411 return 0;
412 }
413 #endif
415 int
416 browse_handle_select(screen_t *screen,
417 mpdclient_t *c,
418 list_window_t *lw,
419 mpdclient_filelist_t *filelist)
420 {
421 filelist_entry_t *entry;
423 if ( filelist==NULL )
424 return -1;
425 entry=( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
426 if( entry==NULL || entry->entity==NULL)
427 return -1;
429 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
430 return load_playlist(screen, c, entry);
432 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
433 mpd_Directory *dir = entry->entity->info.directory;
434 #ifdef USE_OLD_ADD
435 add_directory(c, tmp);
436 #else
437 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 ) {
438 char *tmp = utf8_to_locale(dir->path);
440 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
441 g_free(tmp);
442 }
443 #endif
444 return 0;
445 }
447 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
448 return -1;
450 if( entry->flags & HIGHLIGHT )
451 entry->flags &= ~HIGHLIGHT;
452 else
453 entry->flags |= HIGHLIGHT;
455 if( entry->flags & HIGHLIGHT ) {
456 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
457 mpd_Song *song = entry->entity->info.song;
459 if( mpdclient_cmd_add(c, song) == 0 ) {
460 char buf[BUFSIZE];
462 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
463 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
464 }
465 }
466 } else {
467 /* remove song from playlist */
468 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
469 mpd_Song *song = entry->entity->info.song;
471 if( song ) {
472 int index = playlist_get_index_from_file(c, song->file);
474 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
475 mpdclient_cmd_delete(c, index);
476 }
477 }
478 }
479 return 0;
480 }
482 int
483 browse_handle_select_all (screen_t *screen,
484 mpdclient_t *c,
485 list_window_t *lw,
486 mpdclient_filelist_t *filelist)
487 {
488 filelist_entry_t *entry;
489 GList *temp = filelist->list;
491 if ( filelist==NULL )
492 return -1;
493 for (filelist->list = g_list_first(filelist->list);
494 filelist->list;
495 filelist->list = g_list_next(filelist->list)) {
496 entry=( filelist_entry_t *) filelist->list->data;
497 if( entry==NULL || entry->entity==NULL)
498 return -1;
500 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
501 load_playlist(screen, c, entry);
503 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
504 mpd_Directory *dir = entry->entity->info.directory;
505 #ifdef USE_OLD_ADD
506 add_directory(c, tmp);
507 #else
508 if (mpdclient_cmd_add_path_utf8(c, dir->path) == 0) {
509 char *tmp = utf8_to_locale(dir->path);
511 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
512 g_free(tmp);
513 }
514 #endif
515 }
517 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
518 continue;
520 entry->flags |= HIGHLIGHT;
522 if( entry->flags & HIGHLIGHT ) {
523 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
524 mpd_Song *song = entry->entity->info.song;
526 if( mpdclient_cmd_add(c, song) == 0 ) {
527 char buf[BUFSIZE];
529 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
530 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
531 }
532 }
533 }
534 /*
535 else {
536 //remove song from playlist
537 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
538 mpd_Song *song = entry->entity->info.song;
540 if( song ) {
541 int index = playlist_get_index_from_file(c, song->file);
543 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
544 mpdclient_cmd_delete(c, index);
545 }
546 }
547 }
548 */
549 return 0;
550 }
552 filelist->list = temp;
553 return 0;
554 }
556 static void
557 browse_init(WINDOW *w, int cols, int rows)
558 {
559 lw = list_window_init(w, cols, rows);
560 lw_state = list_window_init_state();
561 }
563 static void
564 browse_resize(int cols, int rows)
565 {
566 lw->cols = cols;
567 lw->rows = rows;
568 }
570 static void
571 browse_exit(void)
572 {
573 if( filelist )
574 filelist = mpdclient_filelist_free(filelist);
575 lw = list_window_free(lw);
576 lw_state = list_window_free_state(lw_state);
577 }
579 static void
580 browse_open(screen_t *screen, mpdclient_t *c)
581 {
582 if( filelist == NULL ) {
583 filelist = mpdclient_filelist_get(c, "");
584 mpdclient_install_playlist_callback(c, playlist_changed_callback);
585 mpdclient_install_browse_callback(c, file_changed_callback);
586 }
587 }
589 static void
590 browse_close(void)
591 {
592 }
594 static const char *
595 browse_title(char *str, size_t size)
596 {
597 char *pathcopy;
598 char *parentdir;
600 pathcopy = strdup(filelist->path);
601 parentdir = dirname(pathcopy);
602 parentdir = basename(parentdir);
604 if( parentdir[0] == '.' && strlen(parentdir) == 1 ) {
605 parentdir = NULL;
606 }
608 g_snprintf(str, size, _("Browse: %s%s%s"),
609 parentdir ? parentdir : "",
610 parentdir ? "/" : "",
611 basename(filelist->path));
612 free(pathcopy);
613 return str;
614 }
616 static void
617 browse_paint(screen_t *screen, mpdclient_t *c)
618 {
619 lw->clear = 1;
621 list_window_paint(lw, browse_lw_callback, (void *) filelist);
622 wnoutrefresh(lw->w);
623 }
625 static void
626 browse_update(screen_t *screen, mpdclient_t *c)
627 {
628 if( filelist->updated ) {
629 browse_paint(screen, c);
630 filelist->updated = FALSE;
631 return;
632 }
634 list_window_paint(lw, browse_lw_callback, (void *) filelist);
635 wnoutrefresh(lw->w);
636 }
639 #ifdef HAVE_GETMOUSE
640 int
641 browse_handle_mouse_event(screen_t *screen,
642 mpdclient_t *c,
643 list_window_t *lw,
644 mpdclient_filelist_t *filelist)
645 {
646 int row;
647 int prev_selected = lw->selected;
648 unsigned long bstate;
649 int length;
651 if ( filelist )
652 length = filelist->length;
653 else
654 length = 0;
656 if( screen_get_mouse_event(c, lw, length, &bstate, &row) )
657 return 1;
659 lw->selected = lw->start+row;
660 list_window_check_selected(lw, length);
662 if( bstate & BUTTON1_CLICKED ) {
663 if( prev_selected == lw->selected )
664 browse_handle_enter(screen, c, lw, filelist);
665 } else if( bstate & BUTTON3_CLICKED ) {
666 if( prev_selected == lw->selected )
667 browse_handle_select(screen, c, lw, filelist);
668 }
670 return 1;
671 }
672 #endif
674 static int
675 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
676 {
677 switch(cmd)
678 {
679 case CMD_PLAY:
680 browse_handle_enter(screen, c, lw, filelist);
681 return 1;
682 case CMD_GO_ROOT_DIRECTORY:
683 return change_directory(screen, c, NULL, "");
684 break;
685 case CMD_GO_PARENT_DIRECTORY:
686 return change_directory(screen, c, NULL, "..");
687 break;
688 case CMD_SELECT:
689 if( browse_handle_select(screen, c, lw, filelist) == 0 )
690 {
691 /* continue and select next item... */
692 cmd = CMD_LIST_NEXT;
693 }
694 break;
695 case CMD_DELETE:
696 handle_delete(screen, c);
697 break;
698 case CMD_SAVE_PLAYLIST:
699 handle_save(screen, c);
700 break;
701 case CMD_SCREEN_UPDATE:
702 screen->painted = 0;
703 lw->clear = 1;
704 lw->repaint = 1;
705 filelist = mpdclient_filelist_update(c, filelist);
706 list_window_check_selected(lw, filelist->length);
707 screen_status_printf(_("Screen updated!"));
708 return 1;
709 case CMD_DB_UPDATE:
710 if( !c->status->updatingDb )
711 {
712 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
713 {
714 if(strcmp(filelist->path,"")) {
715 screen_status_printf(_("Database update of %s started!"),
716 filelist->path);
717 } else {
718 screen_status_printf(_("Database update started!"));
719 }
720 /* set updatingDb to make shure the browse callback gets called
721 * even if the updated has finished before status is updated */
722 c->status->updatingDb = 1;
723 }
724 }
725 else
726 screen_status_printf(_("Database update running..."));
727 return 1;
728 case CMD_LIST_FIND:
729 case CMD_LIST_RFIND:
730 case CMD_LIST_FIND_NEXT:
731 case CMD_LIST_RFIND_NEXT:
732 return screen_find(screen,
733 lw, filelist->length,
734 cmd, browse_lw_callback, (void *) filelist);
735 case CMD_MOUSE_EVENT:
736 return browse_handle_mouse_event(screen,c,lw,filelist);
737 default:
738 break;
739 }
740 return list_window_cmd(lw, filelist->length, cmd);
741 }
743 static list_window_t *
744 get_filelist_window(void)
745 {
746 return lw;
747 }
749 screen_functions_t *
750 get_screen_browse(void)
751 {
752 static screen_functions_t functions;
754 memset(&functions, 0, sizeof(screen_functions_t));
755 functions.init = browse_init;
756 functions.exit = browse_exit;
757 functions.open = browse_open;
758 functions.close = browse_close;
759 functions.resize = browse_resize;
760 functions.paint = browse_paint;
761 functions.update = browse_update;
762 functions.cmd = browse_cmd;
763 functions.get_lw = get_filelist_window;
764 functions.get_title = browse_title;
766 return &functions;
767 }