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 <glib.h>
25 #include <ncurses.h>
27 #include "config.h"
28 #include "ncmpc.h"
29 #include "options.h"
30 #include "support.h"
31 #include "mpdclient.h"
32 #include "strfsong.h"
33 #include "command.h"
34 #include "screen.h"
35 #include "screen_utils.h"
36 #include "screen_browse.h"
37 #include "screen_play.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;
53 /* clear the highlight flag for all items in the filelist */
54 void
55 clear_highlights(mpdclient_filelist_t *filelist)
56 {
57 GList *list = g_list_first(filelist->list);
59 while( list )
60 {
61 filelist_entry_t *entry = list->data;
63 entry->flags &= ~HIGHLIGHT;
64 list = list->next;
65 }
66 }
68 /* change the highlight flag for a song */
69 void
70 set_highlight(mpdclient_filelist_t *filelist, mpd_Song *song, int highlight)
71 {
72 GList *list = g_list_first(filelist->list);
74 if( !song )
75 return;
77 while( list )
78 {
79 filelist_entry_t *entry = list->data;
80 mpd_InfoEntity *entity = entry->entity;
82 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
83 {
84 mpd_Song *song2 = entity->info.song;
86 if( strcmp(song->file, song2->file) == 0 )
87 {
88 if(highlight)
89 entry->flags |= HIGHLIGHT;
90 else
91 entry->flags &= ~HIGHLIGHT;
92 }
93 }
94 list = list->next;
95 }
96 }
98 /* sync highlight flags with playlist */
99 void
100 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *filelist)
101 {
102 GList *list = g_list_first(filelist->list);
104 while(list)
105 {
106 filelist_entry_t *entry = list->data;
107 mpd_InfoEntity *entity = entry->entity;
109 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
110 {
111 mpd_Song *song = entity->info.song;
113 if( playlist_get_index_from_file(c, song->file) >= 0 )
114 entry->flags |= HIGHLIGHT;
115 else
116 entry->flags &= ~HIGHLIGHT;
117 }
118 list=list->next;
119 }
120 }
122 /* the db have changed -> update the filelist */
123 static void
124 file_changed_callback(mpdclient_t *c, int event, gpointer data)
125 {
126 D("screen_file.c> filelist_callback() [%d]\n", event);
127 filelist = mpdclient_filelist_update(c, filelist);
128 sync_highlights(c, filelist);
129 list_window_check_selected(lw, filelist->length);
130 }
132 /* the playlist have been updated -> fix highlights */
133 static void
134 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
135 {
136 D("screen_file.c> playlist_callback() [%d]\n", event);
137 switch(event)
138 {
139 case PLAYLIST_EVENT_CLEAR:
140 clear_highlights(filelist);
141 break;
142 case PLAYLIST_EVENT_ADD:
143 set_highlight(filelist, (mpd_Song *) data, 1);
144 break;
145 case PLAYLIST_EVENT_DELETE:
146 set_highlight(filelist, (mpd_Song *) data, 0);
147 break;
148 case PLAYLIST_EVENT_MOVE:
149 break;
150 default:
151 sync_highlights(c, filelist);
152 break;
153 }
154 }
156 /* list_window callback */
157 char *
158 browse_lw_callback(int index, int *highlight, void *data)
159 {
160 static char buf[BUFSIZE];
161 mpdclient_filelist_t *filelist = (mpdclient_filelist_t *) data;
162 filelist_entry_t *entry;
163 mpd_InfoEntity *entity;
165 *highlight = 0;
166 if( (entry=(filelist_entry_t *)g_list_nth_data(filelist->list,index))==NULL )
167 return NULL;
169 entity = entry->entity;
170 *highlight = (entry->flags & HIGHLIGHT);
172 if( entity == NULL )
173 {
174 return "[..]";
175 }
176 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
177 {
178 mpd_Directory *dir = entity->info.directory;
179 char *dirname = utf8_to_locale(basename(dir->path));
181 g_snprintf(buf, BUFSIZE, "[%s]", dirname);
182 g_free(dirname);
183 return buf;
184 }
185 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
186 {
187 mpd_Song *song = entity->info.song;
189 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
190 return buf;
191 }
192 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
193 {
194 mpd_PlaylistFile *plf = entity->info.playlistFile;
195 char *filename = utf8_to_locale(basename(plf->path));
197 #ifdef USE_OLD_LAYOUT
198 g_snprintf(buf, BUFSIZE, "*%s*", filename);
199 #else
200 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
201 #endif
202 g_free(filename);
203 return buf;
204 }
205 return "Error: Unknown entry!";
206 }
208 /* chdir */
209 static int
210 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry, char *new_path)
211 {
212 mpd_InfoEntity *entity = NULL;
213 gchar *path = NULL;
215 if( entry!=NULL )
216 entity = entry->entity;
217 else if( new_path==NULL )
218 return -1;
220 if( entity==NULL )
221 {
222 if( entry || 0==strcmp(new_path, "..") )
223 {
224 /* return to parent */
225 char *parent = g_path_get_dirname(filelist->path);
226 if( strcmp(parent, ".") == 0 )
227 {
228 parent[0] = '\0';
229 }
230 path = g_strdup(parent);
231 }
232 else
233 {
234 /* entry==NULL, then new_path ("" is root) */
235 path = g_strdup(new_path);
236 }
237 list_window_reset(lw);
238 /* restore previous list window state */
239 list_window_pop_state(lw_state,lw);
240 }
241 else
242 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY)
243 {
244 /* enter sub */
245 mpd_Directory *dir = entity->info.directory;
246 path = utf8_to_locale(dir->path);
247 /* save current list window state */
248 list_window_push_state(lw_state,lw);
249 }
250 else
251 return -1;
253 filelist = mpdclient_filelist_free(filelist);
254 filelist = mpdclient_filelist_get(c, path);
255 sync_highlights(c, filelist);
256 list_window_check_selected(lw, filelist->length);
257 g_free(path);
258 return 0;
259 }
261 static int
262 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
263 {
264 mpd_InfoEntity *entity = entry->entity;
265 mpd_PlaylistFile *plf = entity->info.playlistFile;
266 char *filename = utf8_to_locale(plf->path);
268 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
269 screen_status_printf(_("Loading playlist %s..."), basename(filename));
270 g_free(filename);
271 return 0;
272 }
274 static int
275 handle_save(screen_t *screen, mpdclient_t *c)
276 {
277 filelist_entry_t *entry;
278 char *defaultname = NULL;
281 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
282 if( entry && entry->entity )
283 {
284 mpd_InfoEntity *entity = entry->entity;
285 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
286 {
287 mpd_PlaylistFile *plf = entity->info.playlistFile;
288 defaultname = plf->path;
289 }
290 }
291 return playlist_save(screen, c, NULL, defaultname);
292 }
294 static int
295 handle_delete(screen_t *screen, mpdclient_t *c)
296 {
297 filelist_entry_t *entry;
298 mpd_InfoEntity *entity;
299 mpd_PlaylistFile *plf;
300 char *str, *buf;
301 int key;
303 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
304 if( entry==NULL || entry->entity==NULL )
305 return -1;
307 entity = entry->entity;
309 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
310 {
311 screen_status_printf(_("You can only delete playlists!"));
312 screen_bell();
313 return -1;
314 }
316 plf = entity->info.playlistFile;
317 str = utf8_to_locale(basename(plf->path));
318 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
319 g_free(str);
320 key = tolower(screen_getch(screen->status_window.w, buf));
321 g_free(buf);
322 if( key==KEY_RESIZE )
323 screen_resize();
324 if( key != YES[0] )
325 {
326 screen_status_printf(_("Aborted!"));
327 return 0;
328 }
330 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
331 {
332 return -1;
333 }
334 screen_status_printf(_("Playlist deleted!"));
335 return 0;
336 }
338 static int
339 enqueue_and_play(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
340 {
341 int index;
342 mpd_InfoEntity *entity = entry->entity;
343 mpd_Song *song = entity->info.song;
345 if(!( entry->flags & HIGHLIGHT ))
346 {
347 if( mpdclient_cmd_add(c, song) == 0 )
348 {
349 char buf[BUFSIZE];
351 entry->flags |= HIGHLIGHT;
352 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
353 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
354 mpdclient_update(c); /* get song id */
355 }
356 else
357 return -1;
358 }
360 index = playlist_get_index_from_file(c, song->file);
361 mpdclient_cmd_play(c, index);
362 return 0;
363 }
365 int
366 browse_handle_enter(screen_t *screen,
367 mpdclient_t *c,
368 list_window_t *lw,
369 mpdclient_filelist_t *filelist)
370 {
371 filelist_entry_t *entry;
372 mpd_InfoEntity *entity;
374 if ( filelist==NULL )
375 return -1;
376 entry = ( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
377 if( entry==NULL )
378 return -1;
380 entity = entry->entity;
381 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
382 return change_directory(screen, c, entry, NULL);
383 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
384 return load_playlist(screen, c, entry);
385 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
386 return enqueue_and_play(screen, c, entry);
387 return -1;
388 }
391 #ifdef USE_OLD_ADD
392 /* NOTE - The add_directory functions should move to mpdclient.c */
393 extern gint mpdclient_finish_command(mpdclient_t *c);
395 static int
396 add_directory(mpdclient_t *c, char *dir)
397 {
398 mpd_InfoEntity *entity;
399 GList *subdir_list = NULL;
400 GList *list = NULL;
401 char *dirname;
403 dirname = utf8_to_locale(dir);
404 screen_status_printf(_("Adding directory %s...\n"), dirname);
405 doupdate();
406 g_free(dirname);
407 dirname = NULL;
409 mpd_sendLsInfoCommand(c->connection, dir);
410 mpd_sendCommandListBegin(c->connection);
411 while( (entity=mpd_getNextInfoEntity(c->connection)) )
412 {
413 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
414 {
415 mpd_Song *song = entity->info.song;
416 mpd_sendAddCommand(c->connection, song->file);
417 mpd_freeInfoEntity(entity);
418 }
419 else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
420 {
421 subdir_list = g_list_append(subdir_list, (gpointer) entity);
422 }
423 else
424 mpd_freeInfoEntity(entity);
425 }
426 mpd_sendCommandListEnd(c->connection);
427 mpdclient_finish_command(c);
428 c->need_update = TRUE;
430 list = g_list_first(subdir_list);
431 while( list!=NULL )
432 {
433 mpd_Directory *dir;
435 entity = list->data;
436 dir = entity->info.directory;
437 add_directory(c, dir->path);
438 mpd_freeInfoEntity(entity);
439 list->data=NULL;
440 list=list->next;
441 }
442 g_list_free(subdir_list);
443 return 0;
444 }
445 #endif
447 int
448 browse_handle_select(screen_t *screen,
449 mpdclient_t *c,
450 list_window_t *lw,
451 mpdclient_filelist_t *filelist)
452 {
453 filelist_entry_t *entry;
455 if ( filelist==NULL )
456 return -1;
457 entry=( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
458 if( entry==NULL || entry->entity==NULL)
459 return -1;
461 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
462 return load_playlist(screen, c, entry);
464 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
465 {
466 mpd_Directory *dir = entry->entity->info.directory;
467 #ifdef USE_OLD_ADD
468 add_directory(c, tmp);
469 #else
470 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 )
471 {
472 char *tmp = utf8_to_locale(dir->path);
474 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
475 g_free(tmp);
476 }
477 #endif
478 return 0;
479 }
481 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
482 return -1;
484 if( entry->flags & HIGHLIGHT )
485 entry->flags &= ~HIGHLIGHT;
486 else
487 entry->flags |= HIGHLIGHT;
489 if( entry->flags & HIGHLIGHT )
490 {
491 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
492 {
493 mpd_Song *song = entry->entity->info.song;
495 if( mpdclient_cmd_add(c, song) == 0 )
496 {
497 char buf[BUFSIZE];
499 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
500 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
501 }
502 }
503 }
504 else
505 {
506 /* remove song from playlist */
507 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
508 {
509 mpd_Song *song = entry->entity->info.song;
511 if( song )
512 {
513 int index = playlist_get_index_from_file(c, song->file);
515 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
516 mpdclient_cmd_delete(c, index);
517 }
518 }
519 }
520 return 0;
521 }
523 static void
524 browse_init(WINDOW *w, int cols, int rows)
525 {
526 lw = list_window_init(w, cols, rows);
527 lw_state = list_window_init_state();
528 }
530 static void
531 browse_resize(int cols, int rows)
532 {
533 lw->cols = cols;
534 lw->rows = rows;
535 }
537 static void
538 browse_exit(void)
539 {
540 if( filelist )
541 filelist = mpdclient_filelist_free(filelist);
542 lw = list_window_free(lw);
543 lw_state = list_window_free_state(lw_state);
544 }
546 static void
547 browse_open(screen_t *screen, mpdclient_t *c)
548 {
549 if( filelist == NULL )
550 {
551 filelist = mpdclient_filelist_get(c, "");
552 mpdclient_install_playlist_callback(c, playlist_changed_callback);
553 mpdclient_install_browse_callback(c, file_changed_callback);
554 }
555 }
557 static void
558 browse_close(void)
559 {
560 }
562 static char *
563 browse_title(char *str, size_t size)
564 {
565 char *pathcopy;
566 char *parentdir;
567 pathcopy = strdup(filelist->path);
568 parentdir = dirname(pathcopy);
569 parentdir = basename(parentdir);
570 if( parentdir[0] == '.' && strlen(parentdir) == 1 )
571 {
572 parentdir = NULL;
573 }
574 g_snprintf(str, size, _("Browse: %s%s%s"),
575 parentdir ? parentdir : "",
576 parentdir ? "/" : "",
577 basename(filelist->path));
578 free(pathcopy);
579 return str;
580 }
582 static void
583 browse_paint(screen_t *screen, mpdclient_t *c)
584 {
585 lw->clear = 1;
587 list_window_paint(lw, browse_lw_callback, (void *) filelist);
588 wnoutrefresh(lw->w);
589 }
591 static void
592 browse_update(screen_t *screen, mpdclient_t *c)
593 {
594 if( filelist->updated )
595 {
596 browse_paint(screen, c);
597 filelist->updated = FALSE;
598 return;
599 }
600 list_window_paint(lw, browse_lw_callback, (void *) filelist);
601 wnoutrefresh(lw->w);
602 }
605 #ifdef HAVE_GETMOUSE
606 int
607 browse_handle_mouse_event(screen_t *screen,
608 mpdclient_t *c,
609 list_window_t *lw,
610 mpdclient_filelist_t *filelist)
611 {
612 int row;
613 int prev_selected = lw->selected;
614 unsigned long bstate;
615 int length;
617 if ( filelist )
618 length = filelist->length;
619 else
620 length = 0;
622 if( screen_get_mouse_event(c, lw, length, &bstate, &row) )
623 return 1;
625 lw->selected = lw->start+row;
626 list_window_check_selected(lw, length);
628 if( bstate & BUTTON1_CLICKED )
629 {
630 if( prev_selected == lw->selected )
631 browse_handle_enter(screen, c, lw, filelist);
632 }
633 else if( bstate & BUTTON3_CLICKED )
634 {
635 if( prev_selected == lw->selected )
636 browse_handle_select(screen, c, lw, filelist);
637 }
639 return 1;
640 }
641 #endif
643 static int
644 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
645 {
646 switch(cmd)
647 {
648 case CMD_PLAY:
649 browse_handle_enter(screen, c, lw, filelist);
650 return 1;
651 case CMD_GO_ROOT_DIRECTORY:
652 return change_directory(screen, c, NULL, "");
653 break;
654 case CMD_GO_PARENT_DIRECTORY:
655 return change_directory(screen, c, NULL, "..");
656 break;
657 case CMD_SELECT:
658 if( browse_handle_select(screen, c, lw, filelist) == 0 )
659 {
660 /* continue and select next item... */
661 cmd = CMD_LIST_NEXT;
662 }
663 break;
664 case CMD_DELETE:
665 handle_delete(screen, c);
666 break;
667 case CMD_SAVE_PLAYLIST:
668 handle_save(screen, c);
669 break;
670 case CMD_SCREEN_UPDATE:
671 screen->painted = 0;
672 lw->clear = 1;
673 lw->repaint = 1;
674 filelist = mpdclient_filelist_update(c, filelist);
675 list_window_check_selected(lw, filelist->length);
676 screen_status_printf(_("Screen updated!"));
677 return 1;
678 case CMD_DB_UPDATE:
679 if( !c->status->updatingDb )
680 {
681 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
682 {
683 if(strcmp(filelist->path,"")) {
684 screen_status_printf(_("Database update of %s started!"),
685 filelist->path);
686 } else {
687 screen_status_printf(_("Database update started!"));
688 }
689 /* set updatingDb to make shure the browse callback gets called
690 * even if the updated has finished before status is updated */
691 c->status->updatingDb = 1;
692 }
693 }
694 else
695 screen_status_printf(_("Database update running..."));
696 return 1;
697 case CMD_LIST_FIND:
698 case CMD_LIST_RFIND:
699 case CMD_LIST_FIND_NEXT:
700 case CMD_LIST_RFIND_NEXT:
701 return screen_find(screen, c,
702 lw, filelist->length,
703 cmd, browse_lw_callback, (void *) filelist);
704 case CMD_MOUSE_EVENT:
705 return browse_handle_mouse_event(screen,c,lw,filelist);
706 default:
707 break;
708 }
709 return list_window_cmd(lw, filelist->length, cmd);
710 }
713 list_window_t *
714 get_filelist_window()
715 {
716 return lw;
717 }
722 screen_functions_t *
723 get_screen_browse(void)
724 {
725 static screen_functions_t functions;
727 memset(&functions, 0, sizeof(screen_functions_t));
728 functions.init = browse_init;
729 functions.exit = browse_exit;
730 functions.open = browse_open;
731 functions.close = browse_close;
732 functions.resize = browse_resize;
733 functions.paint = browse_paint;
734 functions.update = browse_update;
735 functions.cmd = browse_cmd;
736 functions.get_lw = get_filelist_window;
737 functions.get_title = browse_title;
739 return &functions;
740 }