002e9cd73261132d5bf3704e2f90194f9d3d411c
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"
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 GList *lw_state_list = 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 /* store current state when entering a subdirectory */
157 static void
158 push_lw_state(void)
159 {
160 list_window_t *tmp = g_malloc(sizeof(list_window_t));
162 memcpy(tmp, lw, sizeof(list_window_t));
163 lw_state_list = g_list_prepend(lw_state_list, (gpointer) tmp);
164 }
166 /* get previous state when leaving a directory */
167 static void
168 pop_lw_state(void)
169 {
170 if( lw_state_list )
171 {
172 list_window_t *tmp = lw_state_list->data;
174 memcpy(lw, tmp, sizeof(list_window_t));
175 g_free(tmp);
176 lw_state_list->data = NULL;
177 lw_state_list = g_list_delete_link(lw_state_list, lw_state_list);
178 }
179 }
181 /* list_window callback */
182 char *
183 browse_lw_callback(int index, int *highlight, void *data)
184 {
185 static char buf[BUFSIZE];
186 mpdclient_filelist_t *filelist = (mpdclient_filelist_t *) data;
187 filelist_entry_t *entry;
188 mpd_InfoEntity *entity;
190 *highlight = 0;
191 if( (entry=(filelist_entry_t *)g_list_nth_data(filelist->list,index))==NULL )
192 return NULL;
194 entity = entry->entity;
195 *highlight = (entry->flags & HIGHLIGHT);
197 if( entity == NULL )
198 {
199 return "[..]";
200 }
201 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
202 {
203 mpd_Directory *dir = entity->info.directory;
204 char *dirname = utf8_to_locale(basename(dir->path));
206 g_snprintf(buf, BUFSIZE, "[%s]", dirname);
207 g_free(dirname);
208 return buf;
209 }
210 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
211 {
212 mpd_Song *song = entity->info.song;
214 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
215 return buf;
216 }
217 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
218 {
219 mpd_PlaylistFile *plf = entity->info.playlistFile;
220 char *filename = utf8_to_locale(basename(plf->path));
222 #ifdef USE_OLD_LAYOUT
223 g_snprintf(buf, BUFSIZE, "*%s*", filename);
224 #else
225 g_snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
226 #endif
227 g_free(filename);
228 return buf;
229 }
230 return "Error: Unknow entry!";
231 }
233 /* chdir */
234 static int
235 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
236 {
237 mpd_InfoEntity *entity = entry->entity;
238 gchar *path = NULL;
240 if( entity==NULL )
241 {
242 /* return to parent */
243 char *parent = g_path_get_dirname(filelist->path);
244 if( strcmp(parent, ".") == 0 )
245 {
246 parent[0] = '\0';
247 }
248 path = g_strdup(parent);
249 list_window_reset(lw);
250 /* restore previous list window state */
251 pop_lw_state();
252 }
253 else
254 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY)
255 {
256 /* enter sub */
257 mpd_Directory *dir = entity->info.directory;
258 path = utf8_to_locale(dir->path);
259 /* save current list window state */
260 push_lw_state();
261 list_window_reset(lw);
262 }
263 else
264 return -1;
266 filelist = mpdclient_filelist_free(filelist);
267 filelist = mpdclient_filelist_get(c, path);
268 sync_highlights(c, filelist);
269 list_window_check_selected(lw, filelist->length);
270 g_free(path);
271 return 0;
272 }
274 static int
275 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
276 {
277 mpd_InfoEntity *entity = entry->entity;
278 mpd_PlaylistFile *plf = entity->info.playlistFile;
279 char *filename = utf8_to_locale(plf->path);
281 if( mpdclient_cmd_load_playlist_utf8(c, plf->path) == 0 )
282 screen_status_printf(_("Loading playlist %s..."), basename(filename));
283 g_free(filename);
284 return 0;
285 }
287 static int
288 handle_save(screen_t *screen, mpdclient_t *c)
289 {
290 filelist_entry_t *entry;
291 char *defaultname = NULL;
294 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
295 if( entry && entry->entity )
296 {
297 mpd_InfoEntity *entity = entry->entity;
298 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
299 {
300 mpd_PlaylistFile *plf = entity->info.playlistFile;
301 defaultname = plf->path;
302 }
303 }
304 return playlist_save(screen, c, NULL, defaultname);
305 }
307 static int
308 handle_delete(screen_t *screen, mpdclient_t *c)
309 {
310 filelist_entry_t *entry;
311 mpd_InfoEntity *entity;
312 mpd_PlaylistFile *plf;
313 char *str, *buf;
314 int key;
316 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
317 if( entry==NULL || entry->entity==NULL )
318 return -1;
320 entity = entry->entity;
322 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
323 {
324 screen_status_printf(_("You can only delete playlists!"));
325 screen_bell();
326 return -1;
327 }
329 plf = entity->info.playlistFile;
330 str = utf8_to_locale(basename(plf->path));
331 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
332 g_free(str);
333 key = tolower(screen_getch(screen->status_window.w, buf));
334 g_free(buf);
335 if( key==KEY_RESIZE )
336 screen_resize();
337 if( key != YES[0] )
338 {
339 screen_status_printf(_("Aborted!"));
340 return 0;
341 }
343 if( mpdclient_cmd_delete_playlist_utf8(c, plf->path) )
344 {
345 return -1;
346 }
347 screen_status_printf(_("Playlist deleted!"));
348 return 0;
349 }
351 static int
352 enqueue_and_play(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
353 {
354 int index;
355 mpd_InfoEntity *entity = entry->entity;
356 mpd_Song *song = entity->info.song;
358 if(!( entry->flags & HIGHLIGHT ))
359 {
360 if( mpdclient_cmd_add(c, song) == 0 )
361 {
362 char buf[BUFSIZE];
364 entry->flags |= HIGHLIGHT;
365 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
366 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
367 mpdclient_update(c); /* get song id */
368 }
369 else
370 return -1;
371 }
373 index = playlist_get_index_from_file(c, song->file);
374 mpdclient_cmd_play(c, index);
375 return 0;
376 }
378 int
379 browse_handle_enter(screen_t *screen,
380 mpdclient_t *c,
381 list_window_t *lw,
382 mpdclient_filelist_t *filelist)
383 {
384 filelist_entry_t *entry;
385 mpd_InfoEntity *entity;
387 if ( filelist==NULL )
388 return -1;
389 entry = ( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
390 if( entry==NULL )
391 return -1;
393 entity = entry->entity;
394 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
395 return change_directory(screen, c, entry);
396 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
397 return load_playlist(screen, c, entry);
398 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
399 return enqueue_and_play(screen, c, entry);
400 return -1;
401 }
404 #ifdef USE_OLD_ADD
405 /* NOTE - The add_directory functions should move to mpdclient.c */
406 extern gint mpdclient_finish_command(mpdclient_t *c);
408 static int
409 add_directory(mpdclient_t *c, char *dir)
410 {
411 mpd_InfoEntity *entity;
412 GList *subdir_list = NULL;
413 GList *list = NULL;
414 char *dirname;
416 dirname = utf8_to_locale(dir);
417 screen_status_printf(_("Adding directory %s...\n"), dirname);
418 doupdate();
419 g_free(dirname);
420 dirname = NULL;
422 mpd_sendLsInfoCommand(c->connection, dir);
423 mpd_sendCommandListBegin(c->connection);
424 while( (entity=mpd_getNextInfoEntity(c->connection)) )
425 {
426 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
427 {
428 mpd_Song *song = entity->info.song;
429 mpd_sendAddCommand(c->connection, song->file);
430 mpd_freeInfoEntity(entity);
431 }
432 else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
433 {
434 subdir_list = g_list_append(subdir_list, (gpointer) entity);
435 }
436 else
437 mpd_freeInfoEntity(entity);
438 }
439 mpd_sendCommandListEnd(c->connection);
440 mpdclient_finish_command(c);
441 c->need_update = TRUE;
443 list = g_list_first(subdir_list);
444 while( list!=NULL )
445 {
446 mpd_Directory *dir;
448 entity = list->data;
449 dir = entity->info.directory;
450 add_directory(c, dir->path);
451 mpd_freeInfoEntity(entity);
452 list->data=NULL;
453 list=list->next;
454 }
455 g_list_free(subdir_list);
456 return 0;
457 }
458 #endif
460 int
461 browse_handle_select(screen_t *screen,
462 mpdclient_t *c,
463 list_window_t *lw,
464 mpdclient_filelist_t *filelist)
465 {
466 filelist_entry_t *entry;
468 if ( filelist==NULL )
469 return -1;
470 entry=( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
471 if( entry==NULL || entry->entity==NULL)
472 return -1;
474 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
475 return load_playlist(screen, c, entry);
477 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
478 {
479 mpd_Directory *dir = entry->entity->info.directory;
480 #ifdef USE_OLD_ADD
481 add_directory(c, tmp);
482 #else
483 if( mpdclient_cmd_add_path_utf8(c, dir->path) == 0 )
484 {
485 char *tmp = utf8_to_locale(dir->path);
487 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
488 g_free(tmp);
489 }
490 #endif
491 return 0;
492 }
494 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
495 return -1;
497 if( entry->flags & HIGHLIGHT )
498 entry->flags &= ~HIGHLIGHT;
499 else
500 entry->flags |= HIGHLIGHT;
502 if( entry->flags & HIGHLIGHT )
503 {
504 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
505 {
506 mpd_Song *song = entry->entity->info.song;
508 if( mpdclient_cmd_add(c, song) == 0 )
509 {
510 char buf[BUFSIZE];
512 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
513 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
514 }
515 }
516 }
517 else
518 {
519 /* remove song from playlist */
520 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
521 {
522 mpd_Song *song = entry->entity->info.song;
524 if( song )
525 {
526 int index = playlist_get_index_from_file(c, song->file);
528 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
529 mpdclient_cmd_delete(c, index);
530 }
531 }
532 }
533 return 0;
534 }
536 static void
537 browse_init(WINDOW *w, int cols, int rows)
538 {
539 lw = list_window_init(w, cols, rows);
540 }
542 static void
543 browse_resize(int cols, int rows)
544 {
545 lw->cols = cols;
546 lw->rows = rows;
547 }
549 static void
550 browse_exit(void)
551 {
552 if( lw_state_list )
553 {
554 GList *list = lw_state_list;
555 while( list )
556 {
557 g_free(list->data);
558 list->data = NULL;
559 list = list->next;
560 }
561 g_list_free(lw_state_list);
562 lw_state_list = NULL;
564 }
565 if( filelist )
566 filelist = mpdclient_filelist_free(filelist);
567 list_window_free(lw);
568 }
570 static void
571 browse_open(screen_t *screen, mpdclient_t *c)
572 {
573 if( filelist == NULL )
574 {
575 filelist = mpdclient_filelist_get(c, "");
576 mpdclient_install_playlist_callback(c, playlist_changed_callback);
577 mpdclient_install_browse_callback(c, file_changed_callback);
578 }
579 }
581 static void
582 browse_close(void)
583 {
584 }
586 static char *
587 browse_title(char *str, size_t size)
588 {
589 g_snprintf(str, size, _("Browse: %s"), basename(filelist->path));
590 return str;
591 }
593 static void
594 browse_paint(screen_t *screen, mpdclient_t *c)
595 {
596 lw->clear = 1;
598 list_window_paint(lw, browse_lw_callback, (void *) filelist);
599 wnoutrefresh(lw->w);
600 }
602 static void
603 browse_update(screen_t *screen, mpdclient_t *c)
604 {
605 if( filelist->updated )
606 {
607 browse_paint(screen, c);
608 filelist->updated = FALSE;
609 return;
610 }
611 list_window_paint(lw, browse_lw_callback, (void *) filelist);
612 wnoutrefresh(lw->w);
613 }
616 #ifdef HAVE_GETMOUSE
617 int
618 browse_handle_mouse_event(screen_t *screen,
619 mpdclient_t *c,
620 list_window_t *lw,
621 mpdclient_filelist_t *filelist)
622 {
623 int row;
624 int prev_selected = lw->selected;
625 unsigned long bstate;
626 int length;
628 if ( filelist )
629 length = filelist->length;
630 else
631 length = 0;
633 if( screen_get_mouse_event(c, lw, length, &bstate, &row) )
634 return 1;
636 lw->selected = lw->start+row;
637 list_window_check_selected(lw, length);
639 if( bstate & BUTTON1_CLICKED )
640 {
641 if( prev_selected == lw->selected )
642 browse_handle_enter(screen, c, lw, filelist);
643 }
644 else if( bstate & BUTTON3_CLICKED )
645 {
646 if( prev_selected == lw->selected )
647 browse_handle_select(screen, c, lw, filelist);
648 }
650 return 1;
651 }
652 #endif
654 static int
655 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
656 {
657 switch(cmd)
658 {
659 case CMD_PLAY:
660 browse_handle_enter(screen, c, lw, filelist);
661 return 1;
662 case CMD_SELECT:
663 if( browse_handle_select(screen, c, lw, filelist) == 0 )
664 {
665 /* continue and select next item... */
666 cmd = CMD_LIST_NEXT;
667 }
668 break;
669 case CMD_DELETE:
670 handle_delete(screen, c);
671 break;
672 case CMD_SAVE_PLAYLIST:
673 handle_save(screen, c);
674 break;
675 case CMD_SCREEN_UPDATE:
676 screen->painted = 0;
677 lw->clear = 1;
678 lw->repaint = 1;
679 filelist = mpdclient_filelist_update(c, filelist);
680 list_window_check_selected(lw, filelist->length);
681 screen_status_printf(_("Screen updated!"));
682 return 1;
683 case CMD_DB_UPDATE:
684 if( !c->status->updatingDb )
685 {
686 if( mpdclient_cmd_db_update_utf8(c,filelist->path)==0 )
687 {
688 screen_status_printf(_("Database update of %s started!"),
689 filelist->path);
690 /* set updatingDb to make shure the browse callback gets called
691 * even if the updated has finished before status is updated */
692 c->status->updatingDb = 1;
693 }
694 }
695 else
696 screen_status_printf(_("Database update running..."));
697 return 1;
698 case CMD_LIST_FIND:
699 case CMD_LIST_RFIND:
700 case CMD_LIST_FIND_NEXT:
701 case CMD_LIST_RFIND_NEXT:
702 return screen_find(screen, c,
703 lw, filelist->length,
704 cmd, browse_lw_callback, (void *) filelist);
705 case CMD_MOUSE_EVENT:
706 return browse_handle_mouse_event(screen,c,lw,filelist);
707 default:
708 break;
709 }
710 return list_window_cmd(lw, filelist->length, cmd);
711 }
714 list_window_t *
715 get_filelist_window()
716 {
717 return lw;
718 }
723 screen_functions_t *
724 get_screen_browse(void)
725 {
726 static screen_functions_t functions;
728 memset(&functions, 0, sizeof(screen_functions_t));
729 functions.init = browse_init;
730 functions.exit = browse_exit;
731 functions.open = browse_open;
732 functions.close = browse_close;
733 functions.resize = browse_resize;
734 functions.paint = browse_paint;
735 functions.update = browse_update;
736 functions.cmd = browse_cmd;
737 functions.get_lw = get_filelist_window;
738 functions.get_title = browse_title;
740 return &functions;
741 }