1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "config.h"
21 #include "screen_browser.h"
22 #include "screen_file.h"
23 #include "screen_song.h"
24 #include "screen_lyrics.h"
25 #include "screen_status.h"
26 #include "screen_find.h"
27 #include "screen.h"
28 #include "i18n.h"
29 #include "options.h"
30 #include "charset.h"
31 #include "strfsong.h"
32 #include "mpdclient.h"
33 #include "filelist.h"
34 #include "colors.h"
35 #include "paint.h"
36 #include "song_paint.h"
38 #include <mpd/client.h>
40 #include <string.h>
42 #define BUFSIZE 1024
44 #ifndef NCMPC_MINI
45 #define HIGHLIGHT (0x01)
46 #endif
48 #ifndef NCMPC_MINI
50 /* sync highlight flags with playlist */
51 void
52 screen_browser_sync_highlights(struct filelist *fl,
53 const struct mpdclient_playlist *playlist)
54 {
55 for (unsigned i = 0; i < filelist_length(fl); ++i) {
56 struct filelist_entry *entry = filelist_get(fl, i);
57 const struct mpd_entity *entity = entry->entity;
59 if (entity != NULL && mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
60 const struct mpd_song *song =
61 mpd_entity_get_song(entity);
63 if (playlist_get_index_from_same_song(playlist,
64 song) >= 0)
65 entry->flags |= HIGHLIGHT;
66 else
67 entry->flags &= ~HIGHLIGHT;
68 }
69 }
70 }
72 #endif
74 /* list_window callback */
75 static const char *
76 browser_lw_callback(unsigned idx, void *data)
77 {
78 const struct filelist *fl = (const struct filelist *) data;
79 static char buf[BUFSIZE];
81 assert(fl != NULL);
82 assert(idx < filelist_length(fl));
84 const struct filelist_entry *entry = filelist_get(fl, idx);
85 assert(entry != NULL);
87 const struct mpd_entity *entity = entry->entity;
89 if( entity == NULL )
90 return "..";
92 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY) {
93 const struct mpd_directory *dir =
94 mpd_entity_get_directory(entity);
95 char *directory = utf8_to_locale(g_basename(mpd_directory_get_path(dir)));
96 g_strlcpy(buf, directory, sizeof(buf));
97 g_free(directory);
98 return buf;
99 } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
100 const struct mpd_song *song = mpd_entity_get_song(entity);
102 strfsong(buf, BUFSIZE, options.list_format, song);
103 return buf;
104 } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
105 const struct mpd_playlist *playlist =
106 mpd_entity_get_playlist(entity);
107 char *filename = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
109 g_strlcpy(buf, filename, sizeof(buf));
110 g_free(filename);
111 return buf;
112 }
114 return "Error: Unknown entry!";
115 }
117 static bool
118 load_playlist(struct mpdclient *c, const struct mpd_playlist *playlist)
119 {
120 struct mpd_connection *connection = mpdclient_get_connection(c);
122 if (connection == NULL)
123 return false;
125 if (mpd_run_load(connection, mpd_playlist_get_path(playlist))) {
126 char *filename = utf8_to_locale(mpd_playlist_get_path(playlist));
127 screen_status_printf(_("Loading playlist %s..."),
128 g_basename(filename));
129 g_free(filename);
131 c->events |= MPD_IDLE_QUEUE;
132 } else
133 mpdclient_handle_error(c);
135 return true;
136 }
138 static bool
139 enqueue_and_play(struct mpdclient *c, struct filelist_entry *entry)
140 {
141 struct mpd_connection *connection = mpdclient_get_connection(c);
142 if (connection == NULL)
143 return false;
145 const struct mpd_song *song = mpd_entity_get_song(entry->entity);
146 int id;
148 #ifndef NCMPC_MINI
149 if (!(entry->flags & HIGHLIGHT))
150 id = -1;
151 else
152 #endif
153 id = playlist_get_id_from_same_song(&c->playlist, song);
155 if (id < 0) {
156 char buf[BUFSIZE];
158 id = mpd_run_add_id(connection, mpd_song_get_uri(song));
159 if (id < 0) {
160 mpdclient_handle_error(c);
161 return false;
162 }
164 #ifndef NCMPC_MINI
165 entry->flags |= HIGHLIGHT;
166 #endif
167 strfsong(buf, BUFSIZE, options.list_format, song);
168 screen_status_printf(_("Adding \'%s\' to queue"), buf);
169 }
171 if (!mpd_run_play_id(connection, id)) {
172 mpdclient_handle_error(c);
173 return false;
174 }
176 return true;
177 }
179 struct filelist_entry *
180 browser_get_selected_entry(const struct screen_browser *browser)
181 {
182 struct list_window_range range;
184 list_window_get_range(browser->lw, &range);
186 if (browser->filelist == NULL ||
187 range.end <= range.start ||
188 range.end > range.start + 1 ||
189 range.start >= filelist_length(browser->filelist))
190 return NULL;
192 return filelist_get(browser->filelist, range.start);
193 }
195 static const struct mpd_entity *
196 browser_get_selected_entity(const struct screen_browser *browser)
197 {
198 const struct filelist_entry *entry = browser_get_selected_entry(browser);
200 return entry != NULL
201 ? entry->entity
202 : NULL;
203 }
205 static const struct mpd_song *
206 browser_get_selected_song(const struct screen_browser *browser)
207 {
208 const struct mpd_entity *entity = browser_get_selected_entity(browser);
210 return entity != NULL &&
211 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG
212 ? mpd_entity_get_song(entity)
213 : NULL;
214 }
216 static struct filelist_entry *
217 browser_get_index(const struct screen_browser *browser, unsigned i)
218 {
219 if (browser->filelist == NULL ||
220 i >= filelist_length(browser->filelist))
221 return NULL;
223 return filelist_get(browser->filelist, i);
224 }
226 static bool
227 browser_handle_enter(struct screen_browser *browser, struct mpdclient *c)
228 {
229 struct filelist_entry *entry = browser_get_selected_entry(browser);
230 if (entry == NULL)
231 return false;
233 struct mpd_entity *entity = entry->entity;
234 if (entity == NULL)
235 return false;
237 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST)
238 return load_playlist(c, mpd_entity_get_playlist(entity));
239 else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
240 return enqueue_and_play(c, entry);
241 return false;
242 }
244 static bool
245 browser_select_entry(struct mpdclient *c, struct filelist_entry *entry,
246 gcc_unused gboolean toggle)
247 {
248 assert(entry != NULL);
249 assert(entry->entity != NULL);
251 if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_PLAYLIST)
252 return load_playlist(c, mpd_entity_get_playlist(entry->entity));
254 if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY) {
255 const struct mpd_directory *dir =
256 mpd_entity_get_directory(entry->entity);
258 if (mpdclient_cmd_add_path(c, mpd_directory_get_path(dir))) {
259 char *tmp = utf8_to_locale(mpd_directory_get_path(dir));
261 screen_status_printf(_("Adding \'%s\' to queue"), tmp);
262 g_free(tmp);
263 }
265 return true;
266 }
268 if (mpd_entity_get_type(entry->entity) != MPD_ENTITY_TYPE_SONG)
269 return false;
271 #ifndef NCMPC_MINI
272 if (!toggle || (entry->flags & HIGHLIGHT) == 0)
273 #endif
274 {
275 const struct mpd_song *song =
276 mpd_entity_get_song(entry->entity);
278 #ifndef NCMPC_MINI
279 entry->flags |= HIGHLIGHT;
280 #endif
282 if (mpdclient_cmd_add(c, song)) {
283 char buf[BUFSIZE];
285 strfsong(buf, BUFSIZE, options.list_format, song);
286 screen_status_printf(_("Adding \'%s\' to queue"), buf);
287 }
288 #ifndef NCMPC_MINI
289 } else {
290 /* remove song from playlist */
291 const struct mpd_song *song =
292 mpd_entity_get_song(entry->entity);
293 int idx;
295 entry->flags &= ~HIGHLIGHT;
297 while ((idx = playlist_get_index_from_same_song(&c->playlist,
298 song)) >= 0)
299 mpdclient_cmd_delete(c, idx);
300 #endif
301 }
303 return true;
304 }
306 static bool
307 browser_handle_select(struct screen_browser *browser, struct mpdclient *c)
308 {
309 struct list_window_range range;
310 bool success = false;
312 list_window_get_range(browser->lw, &range);
313 for (unsigned i = range.start; i < range.end; ++i) {
314 struct filelist_entry *entry = browser_get_index(browser, i);
315 if (entry != NULL && entry->entity != NULL)
316 success = browser_select_entry(c, entry, TRUE);
317 }
319 return range.end == range.start + 1 && success;
320 }
322 static bool
323 browser_handle_add(struct screen_browser *browser, struct mpdclient *c)
324 {
325 struct list_window_range range;
326 bool success = false;
328 list_window_get_range(browser->lw, &range);
329 for (unsigned i = range.start; i < range.end; ++i) {
330 struct filelist_entry *entry = browser_get_index(browser, i);
331 if (entry != NULL && entry->entity != NULL)
332 success = browser_select_entry(c, entry, FALSE) ||
333 success;
334 }
336 return range.end == range.start + 1 && success;
337 }
339 static void
340 browser_handle_select_all(struct screen_browser *browser, struct mpdclient *c)
341 {
342 if (browser->filelist == NULL)
343 return;
345 for (unsigned i = 0; i < filelist_length(browser->filelist); ++i) {
346 struct filelist_entry *entry = filelist_get(browser->filelist, i);
348 if (entry != NULL && entry->entity != NULL)
349 browser_select_entry(c, entry, FALSE);
350 }
351 }
353 #ifdef HAVE_GETMOUSE
355 bool
356 browser_mouse(struct screen_browser *browser,
357 struct mpdclient *c, gcc_unused int x, int row, mmask_t bstate)
358 {
359 unsigned prev_selected = browser->lw->selected;
361 if (list_window_mouse(browser->lw, bstate, row))
362 return true;
364 list_window_set_cursor(browser->lw, browser->lw->start + row);
366 if( bstate & BUTTON1_CLICKED ) {
367 if (prev_selected == browser->lw->selected)
368 browser_handle_enter(browser, c);
369 } else if (bstate & BUTTON3_CLICKED) {
370 if (prev_selected == browser->lw->selected)
371 browser_handle_select(browser, c);
372 }
374 return true;
375 }
377 #endif
379 static void
380 screen_browser_paint_callback(WINDOW *w, unsigned i, unsigned y,
381 unsigned width, bool selected, const void *data);
383 bool
384 browser_cmd(struct screen_browser *browser,
385 struct mpdclient *c, command_t cmd)
386 {
387 if (browser->filelist == NULL)
388 return false;
390 if (list_window_cmd(browser->lw, cmd))
391 return true;
393 switch (cmd) {
394 #if defined(ENABLE_SONG_SCREEN) || defined(ENABLE_LYRICS_SCREEN)
395 const struct mpd_song *song;
396 #endif
398 case CMD_LIST_FIND:
399 case CMD_LIST_RFIND:
400 case CMD_LIST_FIND_NEXT:
401 case CMD_LIST_RFIND_NEXT:
402 screen_find(browser->lw, cmd, browser_lw_callback,
403 browser->filelist);
404 return true;
405 case CMD_LIST_JUMP:
406 screen_jump(browser->lw,
407 browser_lw_callback, browser->filelist,
408 screen_browser_paint_callback, browser);
409 return true;
411 #ifdef ENABLE_SONG_SCREEN
412 case CMD_SCREEN_SONG:
413 song = browser_get_selected_song(browser);
414 if (song == NULL)
415 return false;
417 screen_song_switch(c, song);
418 return true;
419 #endif
421 #ifdef ENABLE_LYRICS_SCREEN
422 case CMD_SCREEN_LYRICS:
423 song = browser_get_selected_song(browser);
424 if (song == NULL)
425 return false;
427 screen_lyrics_switch(c, song, false);
428 return true;
429 #endif
430 case CMD_SCREEN_SWAP:
431 screen_swap(c, browser_get_selected_song(browser));
432 return true;
434 default:
435 break;
436 }
438 if (!mpdclient_is_connected(c))
439 return false;
441 switch (cmd) {
442 const struct mpd_song *song;
444 case CMD_PLAY:
445 browser_handle_enter(browser, c);
446 return true;
448 case CMD_SELECT:
449 if (browser_handle_select(browser, c))
450 list_window_cmd(browser->lw, CMD_LIST_NEXT);
451 return true;
453 case CMD_ADD:
454 if (browser_handle_add(browser, c))
455 list_window_cmd(browser->lw, CMD_LIST_NEXT);
456 return true;
458 case CMD_SELECT_ALL:
459 browser_handle_select_all(browser, c);
460 return true;
462 case CMD_LOCATE:
463 song = browser_get_selected_song(browser);
464 if (song == NULL)
465 return false;
467 screen_file_goto_song(c, song);
468 return true;
470 default:
471 break;
472 }
474 return false;
475 }
477 void
478 screen_browser_paint_directory(WINDOW *w, unsigned width,
479 bool selected, const char *name)
480 {
481 row_color(w, COLOR_DIRECTORY, selected);
483 waddch(w, '[');
484 waddstr(w, name);
485 waddch(w, ']');
487 /* erase the unused space after the text */
488 row_clear_to_eol(w, width, selected);
489 }
491 static void
492 screen_browser_paint_playlist(WINDOW *w, unsigned width,
493 bool selected, const char *name)
494 {
495 row_paint_text(w, width, COLOR_PLAYLIST, selected, name);
496 }
498 static void
499 screen_browser_paint_callback(WINDOW *w, unsigned i,
500 unsigned y, unsigned width,
501 bool selected, const void *data)
502 {
503 const struct screen_browser *browser = (const struct screen_browser *) data;
505 assert(browser != NULL);
506 assert(browser->filelist != NULL);
507 assert(i < filelist_length(browser->filelist));
509 const struct filelist_entry *entry = filelist_get(browser->filelist, i);
510 assert(entry != NULL);
512 const struct mpd_entity *entity = entry->entity;
513 if (entity == NULL) {
514 screen_browser_paint_directory(w, width, selected, "..");
515 return;
516 }
518 #ifndef NCMPC_MINI
519 const bool highlight = (entry->flags & HIGHLIGHT) != 0;
520 #else
521 const bool highlight = false;
522 #endif
524 switch (mpd_entity_get_type(entity)) {
525 const struct mpd_directory *directory;
526 const struct mpd_playlist *playlist;
527 char *p;
529 case MPD_ENTITY_TYPE_DIRECTORY:
530 directory = mpd_entity_get_directory(entity);
531 p = utf8_to_locale(g_basename(mpd_directory_get_path(directory)));
532 screen_browser_paint_directory(w, width, selected, p);
533 g_free(p);
534 break;
536 case MPD_ENTITY_TYPE_SONG:
537 paint_song_row(w, y, width, selected, highlight,
538 mpd_entity_get_song(entity), NULL, browser->song_format);
539 break;
541 case MPD_ENTITY_TYPE_PLAYLIST:
542 playlist = mpd_entity_get_playlist(entity);
543 p = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
544 screen_browser_paint_playlist(w, width, selected, p);
545 g_free(p);
546 break;
548 default:
549 row_paint_text(w, width, highlight ? COLOR_LIST_BOLD : COLOR_LIST,
550 selected, "<unknown>");
551 }
552 }
554 void
555 screen_browser_paint(const struct screen_browser *browser)
556 {
557 list_window_paint2(browser->lw, screen_browser_paint_callback,
558 browser);
559 }