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"
38 #define USE_OLD_LAYOUT
39 #undef USE_OLD_ADD
41 #define BUFSIZE 1024
43 #define HIGHLIGHT (0x01)
46 static list_window_t *lw = NULL;
47 static GList *lw_state_list = NULL;
48 static mpdclient_filelist_t *filelist = NULL;
52 /* clear the highlight flag for all items in the filelist */
53 static void
54 clear_highlights(mpdclient_filelist_t *filelist)
55 {
56 GList *list = g_list_first(filelist->list);
58 while( list )
59 {
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 static void
69 set_highlight(mpdclient_filelist_t *filelist, mpd_Song *song, int highlight)
70 {
71 GList *list = g_list_first(filelist->list);
73 if( !song )
74 return;
76 while( list )
77 {
78 filelist_entry_t *entry = list->data;
79 mpd_InfoEntity *entity = entry->entity;
81 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
82 {
83 mpd_Song *song2 = entity->info.song;
85 if( strcmp(song->file, song2->file) == 0 )
86 {
87 if(highlight)
88 entry->flags |= HIGHLIGHT;
89 else
90 entry->flags &= ~HIGHLIGHT;
91 }
92 }
93 list = list->next;
94 }
95 }
97 /* sync highlight flags with playlist */
98 static void
99 sync_highlights(mpdclient_t *c, mpdclient_filelist_t *filelist)
100 {
101 GList *list = g_list_first(filelist->list);
103 while(list)
104 {
105 filelist_entry_t *entry = list->data;
106 mpd_InfoEntity *entity = entry->entity;
108 if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
109 {
110 mpd_Song *song = entity->info.song;
112 if( playlist_get_index_from_file(c, song->file) >= 0 )
113 entry->flags |= HIGHLIGHT;
114 else
115 entry->flags &= ~HIGHLIGHT;
116 }
117 list=list->next;
118 }
119 }
121 /* the db have changed -> update the filelist */
122 static void
123 file_changed_callback(mpdclient_t *c, int event, gpointer data)
124 {
125 D("screen_file.c> filelist_callback() [%d]\n", event);
126 filelist = mpdclient_filelist_update(c, filelist);
127 sync_highlights(c, filelist);
128 list_window_check_selected(lw, filelist->length);
129 }
131 /* the playlist have been updated -> fix highlights */
132 static void
133 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
134 {
135 D("screen_file.c> playlist_callback() [%d]\n", event);
136 switch(event)
137 {
138 case PLAYLIST_EVENT_CLEAR:
139 clear_highlights(filelist);
140 break;
141 case PLAYLIST_EVENT_ADD:
142 set_highlight(filelist, (mpd_Song *) data, 1);
143 break;
144 case PLAYLIST_EVENT_DELETE:
145 set_highlight(filelist, (mpd_Song *) data, 0);
146 break;
147 case PLAYLIST_EVENT_MOVE:
148 break;
149 default:
150 sync_highlights(c, filelist);
151 break;
152 }
153 }
155 /* store current state when entering a subdirectory */
156 static void
157 push_lw_state(void)
158 {
159 list_window_t *tmp = g_malloc(sizeof(list_window_t));
161 memcpy(tmp, lw, sizeof(list_window_t));
162 lw_state_list = g_list_prepend(lw_state_list, (gpointer) tmp);
163 }
165 /* get previous state when leaving a directory */
166 static void
167 pop_lw_state(void)
168 {
169 if( lw_state_list )
170 {
171 list_window_t *tmp = lw_state_list->data;
173 memcpy(lw, tmp, sizeof(list_window_t));
174 g_free(tmp);
175 lw_state_list->data = NULL;
176 lw_state_list = g_list_delete_link(lw_state_list, lw_state_list);
177 }
178 }
180 /* list_window callback */
181 static char *
182 list_callback(int index, int *highlight, void *data)
183 {
184 static char buf[BUFSIZE];
185 //mpdclient_t *c = (mpdclient_t *) data;
186 filelist_entry_t *entry;
187 mpd_InfoEntity *entity;
189 *highlight = 0;
190 if( (entry=(filelist_entry_t *)g_list_nth_data(filelist->list,index))==NULL )
191 return NULL;
193 entity = entry->entity;
194 *highlight = (entry->flags & HIGHLIGHT);
196 if( entity == NULL )
197 {
198 return "[..]";
199 }
200 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
201 {
202 mpd_Directory *dir = entity->info.directory;
203 char *dirname = utf8_to_locale(basename(dir->path));
205 snprintf(buf, BUFSIZE, "[%s]", dirname);
206 g_free(dirname);
207 return buf;
208 }
209 else if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
210 {
211 mpd_Song *song = entity->info.song;
213 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
214 return buf;
215 }
216 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
217 {
218 mpd_PlaylistFile *plf = entity->info.playlistFile;
219 char *filename = utf8_to_locale(basename(plf->path));
221 #ifdef USE_OLD_LAYOUT
222 snprintf(buf, BUFSIZE, "*%s*", filename);
223 #else
224 snprintf(buf, BUFSIZE, "<Playlist> %s", filename);
225 #endif
226 g_free(filename);
227 return buf;
228 }
229 return "Error: Unknow entry!";
230 }
232 /* chdir */
233 static int
234 change_directory(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
235 {
236 mpd_InfoEntity *entity = entry->entity;
237 gchar *path = NULL;
239 if( entity==NULL )
240 {
241 /* return to parent */
242 char *parent = g_path_get_dirname(filelist->path);
243 if( strcmp(parent, ".") == 0 )
244 {
245 parent[0] = '\0';
246 }
247 path = g_strdup(parent);
248 list_window_reset(lw);
249 /* restore previous list window state */
250 pop_lw_state();
251 }
252 else
253 if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY)
254 {
255 /* enter sub */
256 mpd_Directory *dir = entity->info.directory;
257 path = utf8_to_locale(dir->path);
258 /* save current list window state */
259 push_lw_state();
260 list_window_reset(lw);
261 }
262 else
263 return -1;
265 filelist = mpdclient_filelist_free(filelist);
266 filelist = mpdclient_filelist_get(c, path);
267 sync_highlights(c, filelist);
268 list_window_check_selected(lw, filelist->length);
269 g_free(path);
270 return 0;
271 }
273 static int
274 load_playlist(screen_t *screen, mpdclient_t *c, filelist_entry_t *entry)
275 {
276 mpd_InfoEntity *entity = entry->entity;
277 mpd_PlaylistFile *plf = entity->info.playlistFile;
278 char *filename = utf8_to_locale(plf->path);
280 if( mpdclient_cmd_load_playlist(c, plf->path) == 0 )
281 screen_status_printf(_("Loading playlist %s..."), basename(filename));
282 g_free(filename);
283 return 0;
284 }
286 static int
287 handle_delete(screen_t *screen, mpdclient_t *c)
288 {
289 filelist_entry_t *entry;
290 mpd_InfoEntity *entity;
291 mpd_PlaylistFile *plf;
292 char *str, buf[BUFSIZE];
293 int key;
295 entry=( filelist_entry_t *) g_list_nth_data(filelist->list,lw->selected);
296 if( entry==NULL || entry->entity==NULL )
297 return -1;
299 entity = entry->entity;
301 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
302 {
303 screen_status_printf(_("You can only delete playlists!"));
304 beep();
305 return -1;
306 }
308 plf = entity->info.playlistFile;
309 str = utf8_to_locale(basename(plf->path));
310 snprintf(buf, BUFSIZE, _("Delete playlist %s [%s/%s] ? "), str, YES, NO);
311 g_free(str);
312 key = tolower(screen_getch(screen->status_window.w, buf));
313 if( key==KEY_RESIZE )
314 screen_resize();
315 if( key != YES[0] )
316 {
317 screen_status_printf(_("Aborted!"));
318 return 0;
319 }
321 if( mpdclient_cmd_delete_playlist(c, plf->path) )
322 {
323 return -1;
324 }
325 screen_status_printf(_("Playlist deleted!"));
326 return 0;
327 }
330 static int
331 handle_enter(screen_t *screen, mpdclient_t *c)
332 {
333 filelist_entry_t *entry;
334 mpd_InfoEntity *entity;
336 entry = ( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
337 if( entry==NULL )
338 return -1;
340 entity = entry->entity;
341 if( entity==NULL || entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
342 return change_directory(screen, c, entry);
343 else if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
344 return load_playlist(screen, c, entry);
345 return -1;
346 }
349 #ifdef USE_OLD_ADD
350 /* NOTE - The add_directory functions should move to mpdclient.c */
351 extern gint mpdclient_finish_command(mpdclient_t *c);
353 static int
354 add_directory(mpdclient_t *c, char *dir)
355 {
356 mpd_InfoEntity *entity;
357 GList *subdir_list = NULL;
358 GList *list = NULL;
359 char *dirname;
361 dirname = utf8_to_locale(dir);
362 screen_status_printf(_("Adding directory %s...\n"), dirname);
363 doupdate();
364 g_free(dirname);
365 dirname = NULL;
367 mpd_sendLsInfoCommand(c->connection, dir);
368 mpd_sendCommandListBegin(c->connection);
369 while( (entity=mpd_getNextInfoEntity(c->connection)) )
370 {
371 if( entity->type==MPD_INFO_ENTITY_TYPE_SONG )
372 {
373 mpd_Song *song = entity->info.song;
374 mpd_sendAddCommand(c->connection, song->file);
375 mpd_freeInfoEntity(entity);
376 }
377 else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
378 {
379 subdir_list = g_list_append(subdir_list, (gpointer) entity);
380 }
381 else
382 mpd_freeInfoEntity(entity);
383 }
384 mpd_sendCommandListEnd(c->connection);
385 mpdclient_finish_command(c);
386 c->need_update = TRUE;
388 list = g_list_first(subdir_list);
389 while( list!=NULL )
390 {
391 mpd_Directory *dir;
393 entity = list->data;
394 dir = entity->info.directory;
395 add_directory(c, dir->path);
396 mpd_freeInfoEntity(entity);
397 list->data=NULL;
398 list=list->next;
399 }
400 g_list_free(subdir_list);
401 return 0;
402 }
403 #endif
405 static int
406 handle_select(screen_t *screen, mpdclient_t *c)
407 {
408 filelist_entry_t *entry;
410 entry=( filelist_entry_t *) g_list_nth_data(filelist->list, lw->selected);
411 if( entry==NULL || entry->entity==NULL)
412 return -1;
414 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE )
415 return load_playlist(screen, c, entry);
417 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY )
418 {
419 mpd_Directory *dir = entry->entity->info.directory;
420 #ifdef USE_OLD_ADD
421 add_directory(c, dir->path);
422 #else
423 if( mpdclient_cmd_add_path(c, dir->path) == 0 )
424 {
425 char *tmp = utf8_to_locale(dir->path);
427 screen_status_printf(_("Adding \'%s\' to playlist\n"), tmp);
428 g_free(tmp);
429 }
430 #endif
431 return 0;
432 }
434 if( entry->entity->type!=MPD_INFO_ENTITY_TYPE_SONG )
435 return -1;
437 if( entry->flags & HIGHLIGHT )
438 entry->flags &= ~HIGHLIGHT;
439 else
440 entry->flags |= HIGHLIGHT;
442 if( entry->flags & HIGHLIGHT )
443 {
444 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
445 {
446 mpd_Song *song = entry->entity->info.song;
448 if( mpdclient_cmd_add(c, song) == 0 )
449 {
450 char buf[BUFSIZE];
452 strfsong(buf, BUFSIZE, LIST_FORMAT, song);
453 screen_status_printf(_("Adding \'%s\' to playlist\n"), buf);
454 }
455 }
456 }
457 else
458 {
459 /* remove song from playlist */
460 if( entry->entity->type==MPD_INFO_ENTITY_TYPE_SONG )
461 {
462 mpd_Song *song = entry->entity->info.song;
464 if( song )
465 {
466 int index = playlist_get_index_from_file(c, song->file);
468 while( (index=playlist_get_index_from_file(c, song->file))>=0 )
469 mpdclient_cmd_delete(c, index);
470 }
471 }
472 }
473 return 0;
474 }
476 static void
477 browse_init(WINDOW *w, int cols, int rows)
478 {
479 lw = list_window_init(w, cols, rows);
480 }
482 static void
483 browse_resize(int cols, int rows)
484 {
485 lw->cols = cols;
486 lw->rows = rows;
487 }
489 static void
490 browse_exit(void)
491 {
492 if( lw_state_list )
493 {
494 GList *list = lw_state_list;
495 while( list )
496 {
497 g_free(list->data);
498 list->data = NULL;
499 list = list->next;
500 }
501 g_list_free(lw_state_list);
502 lw_state_list = NULL;
504 }
505 if( filelist )
506 filelist = mpdclient_filelist_free(filelist);
507 list_window_free(lw);
508 }
510 static void
511 browse_open(screen_t *screen, mpdclient_t *c)
512 {
513 if( filelist == NULL )
514 {
515 filelist = mpdclient_filelist_get(c, "");
516 mpdclient_install_playlist_callback(c, playlist_changed_callback);
517 mpdclient_install_browse_callback(c, file_changed_callback);
518 }
519 }
521 static void
522 browse_close(void)
523 {
524 }
526 static char *
527 browse_title(char *str, size_t size)
528 {
529 snprintf(str, size, _("Browse: %s"), basename(filelist->path));
530 return str;
531 }
533 static void
534 browse_paint(screen_t *screen, mpdclient_t *c)
535 {
536 lw->clear = 1;
538 list_window_paint(lw, list_callback, (void *) c);
539 wnoutrefresh(lw->w);
540 }
542 static void
543 browse_update(screen_t *screen, mpdclient_t *c)
544 {
545 if( filelist->updated )
546 {
547 browse_paint(screen, c);
548 filelist->updated = FALSE;
549 return;
550 }
551 list_window_paint(lw, list_callback, (void *) c);
552 wnoutrefresh(lw->w);
553 }
556 static int
557 browse_cmd(screen_t *screen, mpdclient_t *c, command_t cmd)
558 {
559 switch(cmd)
560 {
561 case CMD_PLAY:
562 handle_enter(screen, c);
563 return 1;
564 case CMD_SELECT:
565 if( handle_select(screen, c) == 0 )
566 {
567 /* continue and select next item... */
568 cmd = CMD_LIST_NEXT;
569 }
570 break;
571 case CMD_DELETE:
572 handle_delete(screen, c);
573 break;
574 case CMD_SCREEN_UPDATE:
575 filelist = mpdclient_filelist_update(c, filelist);
576 list_window_check_selected(lw, filelist->length);
577 screen_status_printf(_("Screen updated!"));
578 return 1;
579 case CMD_DB_UPDATE:
580 if( !c->status->updatingDb )
581 {
582 if( mpdclient_cmd_db_update(c,filelist->path)==0 )
583 {
584 screen_status_printf(_("Database update of %s started!"),
585 filelist->path);
586 /* set updatingDb to make shure the browse callback gets called
587 * even if the updated has finished before status is updated */
588 c->status->updatingDb = 1;
589 }
590 }
591 else
592 screen_status_printf(_("Database update running..."));
593 return 1;
594 case CMD_LIST_FIND:
595 case CMD_LIST_RFIND:
596 case CMD_LIST_FIND_NEXT:
597 case CMD_LIST_RFIND_NEXT:
598 return screen_find(screen, c,
599 lw, filelist->length,
600 cmd, list_callback);
601 default:
602 break;
603 }
604 return list_window_cmd(lw, filelist->length, cmd);
605 }
608 list_window_t *
609 get_filelist_window()
610 {
611 return lw;
612 }
617 screen_functions_t *
618 get_screen_browse(void)
619 {
620 static screen_functions_t functions;
622 memset(&functions, 0, sizeof(screen_functions_t));
623 functions.init = browse_init;
624 functions.exit = browse_exit;
625 functions.open = browse_open;
626 functions.close = browse_close;
627 functions.resize = browse_resize;
628 functions.paint = browse_paint;
629 functions.update = browse_update;
630 functions.cmd = browse_cmd;
631 functions.get_lw = get_filelist_window;
632 functions.get_title = browse_title;
634 return &functions;
635 }