Code

002e9cd73261132d5bf3704e2f90194f9d3d411c
[ncmpc.git] / src / screen_file.c
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);
58   
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)
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;
112           
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     }
122 /* the db have changed -> update the filelist */
123 static void 
124 file_changed_callback(mpdclient_t *c, int event, gpointer data)
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);
132 /* the playlist have been updated -> fix highlights */
133 static void 
134 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
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     }
156 /* store current state when entering a subdirectory */
157 static void
158 push_lw_state(void)
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);
166 /* get previous state when leaving a directory */
167 static void
168 pop_lw_state(void)
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     }
181 /* list_window callback */
182 char *
183 browse_lw_callback(int index, int *highlight, void *data)
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!";
233 /* chdir */
234 static int
235 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
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;
274 static int
275 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
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;
287 static int
288 handle_save(screen_t *screen, mpdclient_t *c)
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);
307 static int 
308 handle_delete(screen_t *screen, mpdclient_t *c)
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;
351 static int
352 enqueue_and_play(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
354   int index;
355   mpd_InfoEntity *entity = entry->entity;
356   mpd_Song *song = entity->info.song;
357   
358   if(!( entry->flags & HIGHLIGHT ))
359     {
360       if( mpdclient_cmd_add(c, song) == 0 )
361         {
362           char buf[BUFSIZE];
363           
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     }
372   
373   index = playlist_get_index_from_file(c, song->file);
374   mpdclient_cmd_play(c, index);
375   return 0;
378 int
379 browse_handle_enter(screen_t *screen, 
380                     mpdclient_t *c,
381                     list_window_t *lw,
382                     mpdclient_filelist_t *filelist)
384   filelist_entry_t *entry;
385   mpd_InfoEntity *entity;
386   
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;
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)
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;
442   
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;
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)
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];
511               
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);
527               
528               while( (index=playlist_get_index_from_file(c, song->file))>=0 )
529                 mpdclient_cmd_delete(c, index);
530             }
531         }
532     }
533   return 0;
536 static void
537 browse_init(WINDOW *w, int cols, int rows)
539   lw = list_window_init(w, cols, rows);
542 static void
543 browse_resize(int cols, int rows)
545   lw->cols = cols;
546   lw->rows = rows;
549 static void
550 browse_exit(void)
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);
570 static void 
571 browse_open(screen_t *screen, mpdclient_t *c)
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     }
581 static void
582 browse_close(void)
586 static char *
587 browse_title(char *str, size_t size)
589   g_snprintf(str, size, _("Browse: %s"), basename(filelist->path));
590   return str;
593 static void 
594 browse_paint(screen_t *screen, mpdclient_t *c)
596   lw->clear = 1;
597   
598   list_window_paint(lw, browse_lw_callback, (void *) filelist);
599   wnoutrefresh(lw->w);
602 static void 
603 browse_update(screen_t *screen, mpdclient_t *c)
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);
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)
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;
652 #endif 
654 static int 
655 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
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);
714 list_window_t *
715 get_filelist_window()
717   return lw;
723 screen_functions_t *
724 get_screen_browse(void)
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;