7465ff1255703d3f47b0a7884579f09c99e0d713
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 if( (entry=(filelist_entry_t *)g_list_nth_data(fl->list,idx))==NULL )
160 return NULL;
162 entity = entry->entity;
163 *highlight = (entry->flags & HIGHLIGHT);
165 if( entity == NULL )
166 return "[..]";
168 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
169 mpd_Directory *dir = entity->info.directory;
170 char *directory = utf8_to_locale(basename(dir->path));
172 g_snprintf(buf, BUFSIZE, "[%s]", directory);
173 g_free(directory);
174 return buf;
175 } else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
176 mpd_Song *song = entity->info.song;
178 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
179 return buf;
180 } else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
181 mpd_PlaylistFile *plf = entity->info.playlistFile;
182 char *filename = utf8_to_locale(basename(plf->path));
184 #ifdef USE_OLD_LAYOUT
185 g_snprintf(buf, BUFSIZE, "*%s*", filename);
186 #else
187 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
188 #endif
189 g_free(filename);
190 return buf;
191 }
193 return "Error: Unknown entry!";
194 }
196 /* chdir */
197 static int
198 change_directory(mpd_unused screen_t *screen, mpdclient_t *c,
199 filelist_entry_t *entry, const char *new_path)
200 {
201 mpd_InfoEntity *entity = NULL;
202 gchar *path = NULL;
204 if( entry!=NULL )
205 entity = entry->entity;
206 else if( new_path==NULL )
207 return -1;
209 if( entity==NULL ) {
210 if( entry || 0==strcmp(new_path, "..") ) {
211 /* return to parent */
212 char *parent = g_path_get_dirname(filelist->path);
213 if( strcmp(parent, ".") == 0 )
214 parent[0] = '\0';
215 path = g_strdup(parent);
216 list_window_reset(lw);
217 /* restore previous list window state */
218 list_window_pop_state(lw_state,lw);
219 } else {
220 /* entry==NULL, then new_path ("" is root) */
221 path = g_strdup(new_path);
222 list_window_reset(lw);
223 /* restore first list window state (pop while returning true) */
224 while(list_window_pop_state(lw_state,lw));
225 }
226 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
227 /* enter sub */
228 mpd_Directory *dir = entity->info.directory;
229 path = utf8_to_locale(dir->path);
230 /* save current list window state */
231 list_window_push_state(lw_state,lw);
232 } else
233 return -1;
235 mpdclient_filelist_free(filelist);
236 filelist = mpdclient_filelist_get(c, path);
237 sync_highlights(c, filelist);
238 list_window_check_selected(lw, filelist->length);
239 g_free(path);
240 return 0;
241 }
243 static int
244 load_playlist(mpd_unused screen_t *screen, mpdclient_t *c,
245 filelist_entry_t *entry)
246 {
247 mpd_InfoEntity *entity = entry->entity;
248 mpd_PlaylistFile *plf = entity->info.playlistFile;
249 char *filename = utf8_to_locale(plf->path);
251 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
252 screen_status_printf(_("Loading playlist %s..."), basename(filename));
253 g_free(filename);
254 return 0;
255 }
257 static int
258 handle_save(screen_t *screen, mpdclient_t *c)
259 {
260 filelist_entry_t *entry;
261 char *defaultname = NULL;
264 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
265 if( entry && entry->entity ) {
266 mpd_InfoEntity *entity = entry->entity;
267 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
268 mpd_PlaylistFile *plf = entity->info.playlistFile;
269 defaultname = plf->path;
270 }
271 }
273 return playlist_save(screen, c, NULL, defaultname);
274 }
276 static int
277 handle_delete(screen_t *screen, mpdclient_t *c)
278 {
279 filelist_entry_t *entry;
280 mpd_InfoEntity *entity;
281 mpd_PlaylistFile *plf;
282 char *str, *buf;
283 int key;
285 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
286 if( entry==NULL || entry->entity==NULL )
287 return -1;
289 entity = entry->entity;
291 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
292 screen_status_printf(_("You can only delete playlists!"));
293 screen_bell();
294 return -1;
295 }
297 plf = entity->info.playlistFile;
298 str = utf8_to_locale(basename(plf->path));
299 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
300 g_free(str);
301 key = tolower(screen_getch(screen->status_window.w, buf));
302 g_free(buf);
303 if( key==KEY_RESIZE )
304 screen_resize();
305 if( key != YES[0] ) {
306 screen_status_printf(_("Aborted!"));
307 return 0;
308 }
310 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
311 return -1;
313 screen_status_printf(_("Playlist deleted!"));
314 return 0;
315 }
317 static int
318 enqueue_and_play(mpd_unused screen_t *screen, mpdclient_t *c,
319 filelist_entry_t *entry)
320 {
321 int idx;
322 mpd_InfoEntity *entity = entry->entity;
323 mpd_Song *song = entity->info.song;
325 if(!( entry->flags & HIGHLIGHT )) {
326 if( mpdclient_cmd_add(c, song) == 0 ) {
327 char buf[BUFSIZE];
329 entry->flags |= HIGHLIGHT;
330 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
331 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
332 mpdclient_update(c); /* get song id */
333 } else
334 return -1;
335 }
337 idx = playlist_get_index_from_file(c, song->file);
338 mpdclient_cmd_play(c, idx);
339 return 0;
340 }
342 int
343 browse_handle_enter(screen_t *screen,
344 mpdclient_t *c,
345 list_window_t *local_lw,
346 mpdclient_filelist_t *fl)
347 {
348 filelist_entry_t *entry;
349 mpd_InfoEntity *entity;
351 if ( fl==NULL )
352 return -1;
353 entry = ( filelist_entry_t *) g_list_nth_data(fl->list, local_lw->selected);
354 if( entry==NULL )
355 return -1;
357 entity = entry->entity;
358 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
359 return change_directory(screen, c, entry, NULL);
360 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
361 return load_playlist(screen, c, entry);
362 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
363 return enqueue_and_play(screen, c, entry);
364 return -1;
365 }
368 #ifdef USE_OLD_ADD
369 /* NOTE - The add_directory functions should move to mpdclient.c */
370 extern gint mpdclient_finish_command(mpdclient_t *c);
372 static int
373 add_directory(mpdclient_t *c, char *dir)
374 {
375 mpd_InfoEntity *entity;
376 GList *subdir_list = NULL;
377 GList *list = NULL;
378 char *dirname;
380 dirname = utf8_to_locale(dir);
381 screen_status_printf(_("Adding directory %s...\n"), dirname);
382 doupdate();
383 g_free(dirname);
384 dirname = NULL;
386 mpd_sendLsInfoCommand(c->connection, dir);
387 mpd_sendCommandListBegin(c->connection);
388 while( (entity=mpd_getNextInfoEntity(c->connection)) ) {
389 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
390 mpd_Song *song = entity->info.song;
391 mpd_sendAddCommand(c->connection, song->file);
392 mpd_freeInfoEntity(entity);
393 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
394 subdir_list = g_list_append(subdir_list, (gpointer) entity);
395 } else
396 mpd_freeInfoEntity(entity);
397 }
398 mpd_sendCommandListEnd(c->connection);
399 mpdclient_finish_command(c);
400 c->need_update = TRUE;
402 list = g_list_first(subdir_list);
403 while( list!=NULL ) {
404 mpd_Directory *dir;
406 entity = list->data;
407 dir = entity->info.directory;
408 add_directory(c, dir->path);
409 mpd_freeInfoEntity(entity);
410 list->data=NULL;
411 list=list->next;
412 }
413 g_list_free(subdir_list);
414 return 0;
415 }
416 #endif
418 int
419 browse_handle_select(screen_t *screen,
420 mpdclient_t *c,
421 list_window_t *local_lw,
422 mpdclient_filelist_t *fl)
423 {
424 filelist_entry_t *entry;
426 if ( fl==NULL )
427 return -1;
428 entry=( filelist_entry_t *) g_list_nth_data(fl->list,
429 local_lw->selected);
430 if( entry==NULL || entry->entity==NULL)
431 return -1;
433 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
434 return load_playlist(screen, c, entry);
436 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
437 mpd_Directory *dir = entry->entity->info.directory;
438 #ifdef USE_OLD_ADD
439 add_directory(c, tmp);
440 #else
441 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 ) {
442 char *tmp = utf8_to_locale(dir->path);
444 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
445 g_free(tmp);
446 }
447 #endif
448 return 0;
449 }
451 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
452 return -1;
454 if( entry->flags & HIGHLIGHT )
455 entry->flags &= ~HIGHLIGHT;
456 else
457 entry->flags |= HIGHLIGHT;
459 if( entry->flags & HIGHLIGHT ) {
460 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
461 mpd_Song *song = entry->entity->info.song;
463 if( mpdclient_cmd_add(c, song) == 0 ) {
464 char buf[BUFSIZE];
466 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
467 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
468 }
469 }
470 } else {
471 /* remove song from playlist */
472 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
473 mpd_Song *song = entry->entity->info.song;
475 if( song ) {
476 int idx;
478 while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
479 mpdclient_cmd_delete(c, idx);
480 }
481 }
482 }
483 return 0;
484 }
486 int
487 browse_handle_select_all (screen_t *screen,
488 mpdclient_t *c,
489 mpd_unused list_window_t *local_lw,
490 mpdclient_filelist_t *fl)
491 {
492 filelist_entry_t *entry;
493 GList *temp = fl->list;
495 if ( fl==NULL )
496 return -1;
497 for (fl->list = g_list_first(fl->list);
498 fl->list;
499 fl->list = g_list_next(fl->list)) {
500 entry=( filelist_entry_t *) fl->list->data;
501 if( entry==NULL || entry->entity==NULL)
502 return -1;
504 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
505 load_playlist(screen, c, entry);
507 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY ) {
508 mpd_Directory *dir = entry->entity->info.directory;
509 #ifdef USE_OLD_ADD
510 add_directory(c, tmp);
511 #else
512 if (mpdclient_cmd_add_path_utf8(c, dir->path) == 0) {
513 char *tmp = utf8_to_locale(dir->path);
515 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
516 g_free(tmp);
517 }
518 #endif
519 }
521 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
522 continue;
524 entry->flags |= HIGHLIGHT;
526 if( entry->flags & HIGHLIGHT ) {
527 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
528 mpd_Song *song = entry->entity->info.song;
530 if( mpdclient_cmd_add(c, song) == 0 ) {
531 char buf[BUFSIZE];
533 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
534 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
535 }
536 }
537 }
538 /*
539 else {
540 //remove song from playlist
541 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG ) {
542 mpd_Song *song = entry->entity->info.song;
544 if( song ) {
545 int idx = playlist_get_index_from_file(c, song->file);
547 while( (idx=playlist_get_index_from_file(c, song->file))>=0 )
548 mpdclient_cmd_delete(c, idx);
549 }
550 }
551 }
552 */
553 return 0;
554 }
556 fl->list = temp;
557 return 0;
558 }
560 static void
561 browse_init(WINDOW *w, int cols, int rows)
562 {
563 lw = list_window_init(w, cols, rows);
564 lw_state = list_window_init_state();
565 }
567 static void
568 browse_resize(int cols, int rows)
569 {
570 lw->cols = cols;
571 lw->rows = rows;
572 }
574 static void
575 browse_exit(void)
576 {
577 if( filelist )
578 mpdclient_filelist_free(filelist);
579 list_window_free(lw);
580 list_window_free_state(lw_state);
581 }
583 static void
584 browse_open(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
585 {
586 if( filelist == NULL ) {
587 filelist = mpdclient_filelist_get(c, "");
588 mpdclient_install_playlist_callback(c, playlist_changed_callback);
589 mpdclient_install_browse_callback(c, file_changed_callback);
590 }
591 }
593 static const char *
594 browse_title(char *str, size_t size)
595 {
596 char *pathcopy;
597 char *parentdir;
599 pathcopy = strdup(filelist->path);
600 parentdir = dirname(pathcopy);
601 parentdir = basename(parentdir);
603 if( parentdir[0] == '.' && strlen(parentdir) == 1 ) {
604 parentdir = NULL;
605 }
607 g_snprintf(str, size, _("Browse: %s%s%s"),
608 parentdir ? parentdir : "",
609 parentdir ? "/" : "",
610 basename(filelist->path));
611 free(pathcopy);
612 return str;
613 }
615 static void
616 browse_paint(mpd_unused screen_t *screen, mpd_unused mpdclient_t *c)
617 {
618 lw->clear = 1;
620 list_window_paint(lw, browse_lw_callback, (void *) filelist);
621 wnoutrefresh(lw->w);
622 }
624 static void
625 browse_update(screen_t *screen, mpdclient_t *c)
626 {
627 if( filelist->updated ) {
628 browse_paint(screen, c);
629 filelist->updated = FALSE;
630 return;
631 }
633 list_window_paint(lw, browse_lw_callback, (void *) filelist);
634 wnoutrefresh(lw->w);
635 }
638 #ifdef HAVE_GETMOUSE
639 int
640 browse_handle_mouse_event(screen_t *screen,
641 mpdclient_t *c,
642 list_window_t *local_lw,
643 mpdclient_filelist_t *fl)
644 {
645 int row;
646 unsigned prev_selected = local_lw->selected;
647 unsigned long bstate;
648 int length;
650 if ( fl )
651 length = fl->length;
652 else
653 length = 0;
655 if( screen_get_mouse_event(c, local_lw, length, &bstate, &row) )
656 return 1;
658 local_lw->selected = local_lw->start+row;
659 list_window_check_selected(local_lw, length);
661 if( bstate & BUTTON1_CLICKED ) {
662 if( prev_selected == local_lw->selected )
663 browse_handle_enter(screen, c, local_lw, fl);
664 } else if( bstate & BUTTON3_CLICKED ) {
665 if( prev_selected == local_lw->selected )
666 browse_handle_select(screen, c, local_lw, fl);
667 }
669 return 1;
670 }
671 #endif
673 static int
674 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
675 {
676 switch(cmd)
677 {
678 case CMD_PLAY:
679 browse_handle_enter(screen, c, lw, filelist);
680 return 1;
681 case CMD_GO_ROOT_DIRECTORY:
682 return change_directory(screen, c, NULL, "");
683 break;
684 case CMD_GO_PARENT_DIRECTORY:
685 return change_directory(screen, c, NULL, "..");
686 break;
687 case CMD_SELECT:
688 if( browse_handle_select(screen, c, lw, filelist) == 0 )
689 {
690 /* continue and select next item... */
691 cmd = CMD_LIST_NEXT;
692 }
693 break;
694 case CMD_DELETE:
695 handle_delete(screen, c);
696 break;
697 case CMD_SAVE_PLAYLIST:
698 handle_save(screen, c);
699 break;
700 case CMD_SCREEN_UPDATE:
701 screen->painted = 0;
702 lw->clear = 1;
703 lw->repaint = 1;
704 filelist = mpdclient_filelist_update(c, filelist);
705 list_window_check_selected(lw, filelist->length);
706 screen_status_printf(_("Screen updated!"));
707 return 1;
708 case CMD_DB_UPDATE:
709 if( !c->status->updatingDb )
710 {
711 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
712 {
713 if(strcmp(filelist->path,"")) {
714 screen_status_printf(_("Database update of %s started!"),
715 filelist->path);
716 } else {
717 screen_status_printf(_("Database update started!"));
718 }
719 /* set updatingDb to make shure the browse callback gets called
720 * even if the updated has finished before status is updated */
721 c->status->updatingDb = 1;
722 }
723 }
724 else
725 screen_status_printf(_("Database update running..."));
726 return 1;
727 case CMD_LIST_FIND:
728 case CMD_LIST_RFIND:
729 case CMD_LIST_FIND_NEXT:
730 case CMD_LIST_RFIND_NEXT:
731 return screen_find(screen,
732 lw, filelist->length,
733 cmd, browse_lw_callback, (void *) filelist);
734 case CMD_MOUSE_EVENT:
735 return browse_handle_mouse_event(screen,c,lw,filelist);
736 default:
737 break;
738 }
739 return list_window_cmd(lw, filelist->length, cmd);
740 }
742 const struct screen_functions screen_browse = {
743 .init = browse_init,
744 .exit = browse_exit,
745 .open = browse_open,
746 .resize = browse_resize,
747 .paint = browse_paint,
748 .update = browse_update,
749 .cmd = browse_cmd,
750 .get_title = browse_title,
751 };