ListWindow: convert list_window_callback_fn_t to an abstract class
[ncmpc-debian.git] / src / screen_browser.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2018 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  */
19
20 #include "config.h"
21 #include "screen_browser.hxx"
22 #include "screen_file.hxx"
23 #include "screen_song.hxx"
24 #include "screen_lyrics.hxx"
25 #include "screen_status.hxx"
26 #include "screen_find.hxx"
27 #include "screen.hxx"
28 #include "i18n.h"
29 #include "options.hxx"
30 #include "charset.hxx"
31 #include "strfsong.hxx"
32 #include "mpdclient.hxx"
33 #include "filelist.hxx"
34 #include "colors.hxx"
35 #include "paint.hxx"
36 #include "song_paint.hxx"
37
38 #include <mpd/client.h>
39
40 #include <string.h>
41
42 #define BUFSIZE 1024
43
44 #ifndef NCMPC_MINI
45 #define HIGHLIGHT  (0x01)
46 #endif
47
48 FileListPage::~FileListPage()
49 {
50         delete filelist;
51 }
52
53 #ifndef NCMPC_MINI
54
55 /* sync highlight flags with playlist */
56 void
57 screen_browser_sync_highlights(FileList *fl, const MpdQueue *playlist)
58 {
59         for (unsigned i = 0; i < fl->size(); ++i) {
60                 auto &entry = (*fl)[i];
61                 const auto *entity = entry.entity;
62
63                 if (entity != nullptr && mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
64                         const auto *song = mpd_entity_get_song(entity);
65
66                         if (playlist->FindUri(*song) >= 0)
67                                 entry.flags |= HIGHLIGHT;
68                         else
69                                 entry.flags &= ~HIGHLIGHT;
70                 }
71         }
72 }
73
74 #endif
75
76 const char *
77 FileListPage::GetListItemText(unsigned idx) const
78 {
79         static char buf[BUFSIZE];
80
81         assert(filelist != nullptr);
82         assert(idx < filelist->size());
83
84         const auto &entry = (*filelist)[idx];
85         const auto *entity = entry.entity;
86
87         if( entity == nullptr )
88                 return "..";
89
90         if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY) {
91                 const auto *dir = mpd_entity_get_directory(entity);
92                 const char *name = g_basename(mpd_directory_get_path(dir));
93                 g_strlcpy(buf, Utf8ToLocale(name).c_str(), sizeof(buf));
94                 return buf;
95         } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
96                 const auto *song = mpd_entity_get_song(entity);
97
98                 strfsong(buf, BUFSIZE, options.list_format, song);
99                 return buf;
100         } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
101                 const auto *playlist = mpd_entity_get_playlist(entity);
102                 const char *name = g_basename(mpd_playlist_get_path(playlist));
103                 g_strlcpy(buf, Utf8ToLocale(name).c_str(), sizeof(buf));
104                 return buf;
105         }
106
107         return "Error: Unknown entry!";
108 }
109
110 static bool
111 load_playlist(struct mpdclient *c, const struct mpd_playlist *playlist)
112 {
113         auto *connection = mpdclient_get_connection(c);
114
115         if (connection == nullptr)
116                 return false;
117
118         if (mpd_run_load(connection, mpd_playlist_get_path(playlist))) {
119                 const char *name = g_basename(mpd_playlist_get_path(playlist));
120                 screen_status_printf(_("Loading playlist %s..."),
121                                      Utf8ToLocale(name).c_str());
122
123                 c->events |= MPD_IDLE_QUEUE;
124         } else
125                 mpdclient_handle_error(c);
126
127         return true;
128 }
129
130 static bool
131 enqueue_and_play(struct mpdclient *c, FileListEntry *entry)
132 {
133         auto *connection = mpdclient_get_connection(c);
134         if (connection == nullptr)
135                 return false;
136
137         const auto *song = mpd_entity_get_song(entry->entity);
138         int id;
139
140 #ifndef NCMPC_MINI
141         if (!(entry->flags & HIGHLIGHT))
142                 id = -1;
143         else
144 #endif
145                 id = c->playlist.FindUri(*song);
146
147         if (id < 0) {
148                 char buf[BUFSIZE];
149
150                 id = mpd_run_add_id(connection, mpd_song_get_uri(song));
151                 if (id < 0) {
152                         mpdclient_handle_error(c);
153                         return false;
154                 }
155
156 #ifndef NCMPC_MINI
157                 entry->flags |= HIGHLIGHT;
158 #endif
159                 strfsong(buf, BUFSIZE, options.list_format, song);
160                 screen_status_printf(_("Adding \'%s\' to queue"), buf);
161         }
162
163         if (!mpd_run_play_id(connection, id)) {
164                 mpdclient_handle_error(c);
165                 return false;
166         }
167
168         return true;
169 }
170
171 FileListEntry *
172 FileListPage::GetSelectedEntry() const
173 {
174         const auto range = lw.GetRange();
175
176         if (filelist == nullptr ||
177             range.empty() ||
178             range.end_index > range.start_index + 1 ||
179             range.start_index >= filelist->size())
180                 return nullptr;
181
182         return &(*filelist)[range.start_index];
183 }
184
185 const struct mpd_entity *
186 FileListPage::GetSelectedEntity() const
187 {
188         const auto *entry = GetSelectedEntry();
189
190         return entry != nullptr
191                 ? entry->entity
192                 : nullptr;
193 }
194
195 const struct mpd_song *
196 FileListPage::GetSelectedSong() const
197 {
198         const auto *entity = GetSelectedEntity();
199
200         return entity != nullptr &&
201                 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG
202                 ? mpd_entity_get_song(entity)
203                 : nullptr;
204 }
205
206 FileListEntry *
207 FileListPage::GetIndex(unsigned i) const
208 {
209         if (filelist == nullptr || i >= filelist->size())
210                 return nullptr;
211
212         return &(*filelist)[i];
213 }
214
215 bool
216 FileListPage::HandleEnter(struct mpdclient &c)
217 {
218         auto *entry = GetSelectedEntry();
219         if (entry == nullptr)
220                 return false;
221
222         auto *entity = entry->entity;
223         if (entity == nullptr)
224                 return false;
225
226         if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST)
227                 return load_playlist(&c, mpd_entity_get_playlist(entity));
228         else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
229                 return enqueue_and_play(&c, entry);
230         return false;
231 }
232
233 static bool
234 browser_select_entry(struct mpdclient *c, FileListEntry *entry,
235                      gcc_unused bool toggle)
236 {
237         assert(entry != nullptr);
238         assert(entry->entity != nullptr);
239
240         if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_PLAYLIST)
241                 return load_playlist(c, mpd_entity_get_playlist(entry->entity));
242
243         if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY) {
244                 const auto *dir = mpd_entity_get_directory(entry->entity);
245
246                 if (mpdclient_cmd_add_path(c, mpd_directory_get_path(dir))) {
247                         screen_status_printf(_("Adding \'%s\' to queue"),
248                                              Utf8ToLocale(mpd_directory_get_path(dir)).c_str());
249                 }
250
251                 return true;
252         }
253
254         if (mpd_entity_get_type(entry->entity) != MPD_ENTITY_TYPE_SONG)
255                 return false;
256
257 #ifndef NCMPC_MINI
258         if (!toggle || (entry->flags & HIGHLIGHT) == 0)
259 #endif
260         {
261                 const auto *song = mpd_entity_get_song(entry->entity);
262
263 #ifndef NCMPC_MINI
264                 entry->flags |= HIGHLIGHT;
265 #endif
266
267                 if (mpdclient_cmd_add(c, song)) {
268                         char buf[BUFSIZE];
269
270                         strfsong(buf, BUFSIZE, options.list_format, song);
271                         screen_status_printf(_("Adding \'%s\' to queue"), buf);
272                 }
273 #ifndef NCMPC_MINI
274         } else {
275                 /* remove song from playlist */
276                 const auto *song = mpd_entity_get_song(entry->entity);
277                 int idx;
278
279                 entry->flags &= ~HIGHLIGHT;
280
281                 while ((idx = c->playlist.FindUri(*song)) >= 0)
282                         mpdclient_cmd_delete(c, idx);
283 #endif
284         }
285
286         return true;
287 }
288
289 bool
290 FileListPage::HandleSelect(struct mpdclient &c)
291 {
292         bool success = false;
293
294         const auto range = lw.GetRange();
295         for (const unsigned i : range) {
296                 auto *entry = GetIndex(i);
297                 if (entry != nullptr && entry->entity != nullptr)
298                         success = browser_select_entry(&c, entry, true);
299         }
300
301         SetDirty();
302
303         return range.end_index == range.start_index + 1 && success;
304 }
305
306 bool
307 FileListPage::HandleAdd(struct mpdclient &c)
308 {
309         bool success = false;
310
311         const auto range = lw.GetRange();
312         for (const unsigned i : range) {
313                 auto *entry = GetIndex(i);
314                 if (entry != nullptr && entry->entity != nullptr)
315                         success = browser_select_entry(&c, entry, false) ||
316                                 success;
317         }
318
319         return range.end_index == range.start_index + 1 && success;
320 }
321
322 void
323 FileListPage::HandleSelectAll(struct mpdclient &c)
324 {
325         if (filelist == nullptr)
326                 return;
327
328         for (unsigned i = 0; i < filelist->size(); ++i) {
329                 auto &entry = (*filelist)[i];
330
331                 if (entry.entity != nullptr)
332                         browser_select_entry(&c, &entry, false);
333         }
334
335         SetDirty();
336 }
337
338 #ifdef HAVE_GETMOUSE
339
340 bool
341 FileListPage::OnMouse(struct mpdclient &c, Point p,
342                       mmask_t bstate)
343 {
344         unsigned prev_selected = lw.selected;
345
346         if (ListPage::OnMouse(c, p, bstate))
347                 return true;
348
349         lw.SetCursor(lw.start + p.y);
350
351         if( bstate & BUTTON1_CLICKED ) {
352                 if (prev_selected == lw.selected)
353                         HandleEnter(c);
354         } else if (bstate & BUTTON3_CLICKED) {
355                 if (prev_selected == lw.selected)
356                         HandleSelect(c);
357         }
358
359         return true;
360 }
361
362 #endif
363
364 bool
365 FileListPage::OnCommand(struct mpdclient &c, command_t cmd)
366 {
367         if (filelist == nullptr)
368                 return false;
369
370         if (ListPage::OnCommand(c, cmd))
371                 return true;
372
373         switch (cmd) {
374 #if defined(ENABLE_SONG_SCREEN) || defined(ENABLE_LYRICS_SCREEN)
375                 const struct mpd_song *song;
376 #endif
377
378         case CMD_LIST_FIND:
379         case CMD_LIST_RFIND:
380         case CMD_LIST_FIND_NEXT:
381         case CMD_LIST_RFIND_NEXT:
382                 screen_find(screen, &lw, cmd, *this);
383                 SetDirty();
384                 return true;
385         case CMD_LIST_JUMP:
386                 screen_jump(screen, &lw, *this, *this);
387                 SetDirty();
388                 return true;
389
390 #ifdef ENABLE_SONG_SCREEN
391         case CMD_SCREEN_SONG:
392                 song = GetSelectedSong();
393                 if (song == nullptr)
394                         return false;
395
396                 screen_song_switch(screen, c, *song);
397                 return true;
398 #endif
399
400 #ifdef ENABLE_LYRICS_SCREEN
401         case CMD_SCREEN_LYRICS:
402                 song = GetSelectedSong();
403                 if (song == nullptr)
404                         return false;
405
406                 screen_lyrics_switch(screen, c, *song, false);
407                 return true;
408 #endif
409         case CMD_SCREEN_SWAP:
410                 screen.Swap(c, GetSelectedSong());
411                 return true;
412
413         default:
414                 break;
415         }
416
417         if (!c.IsConnected())
418                 return false;
419
420         switch (cmd) {
421                 const struct mpd_song *song;
422
423         case CMD_PLAY:
424                 HandleEnter(c);
425                 return true;
426
427         case CMD_SELECT:
428                 if (HandleSelect(c))
429                         lw.HandleCommand(CMD_LIST_NEXT);
430                 SetDirty();
431                 return true;
432
433         case CMD_ADD:
434                 if (HandleAdd(c))
435                         lw.HandleCommand(CMD_LIST_NEXT);
436                 SetDirty();
437                 return true;
438
439         case CMD_SELECT_ALL:
440                 HandleSelectAll(c);
441                 return true;
442
443         case CMD_LOCATE:
444                 song = GetSelectedSong();
445                 if (song == nullptr)
446                         return false;
447
448                 screen_file_goto_song(screen, c, *song);
449                 return true;
450
451         default:
452                 break;
453         }
454
455         return false;
456 }
457
458 void
459 screen_browser_paint_directory(WINDOW *w, unsigned width,
460                                bool selected, const char *name)
461 {
462         row_color(w, COLOR_DIRECTORY, selected);
463
464         waddch(w, '[');
465         waddstr(w, name);
466         waddch(w, ']');
467
468         /* erase the unused space after the text */
469         row_clear_to_eol(w, width, selected);
470 }
471
472 static void
473 screen_browser_paint_playlist(WINDOW *w, unsigned width,
474                               bool selected, const char *name)
475 {
476         row_paint_text(w, width, COLOR_PLAYLIST, selected, name);
477 }
478
479 void
480 FileListPage::PaintListItem(WINDOW *w, unsigned i,
481                             unsigned y, unsigned width,
482                             bool selected) const
483 {
484         assert(filelist != nullptr);
485         assert(i < filelist->size());
486
487         const auto &entry = (*filelist)[i];
488         const struct mpd_entity *entity = entry.entity;
489         if (entity == nullptr) {
490                 screen_browser_paint_directory(w, width, selected, "..");
491                 return;
492         }
493
494 #ifndef NCMPC_MINI
495         const bool highlight = (entry.flags & HIGHLIGHT) != 0;
496 #else
497         const bool highlight = false;
498 #endif
499
500         switch (mpd_entity_get_type(entity)) {
501                 const struct mpd_directory *directory;
502                 const struct mpd_playlist *playlist;
503                 const char *name;
504
505         case MPD_ENTITY_TYPE_DIRECTORY:
506                 directory = mpd_entity_get_directory(entity);
507                 name = g_basename(mpd_directory_get_path(directory));
508                 screen_browser_paint_directory(w, width, selected,
509                                                Utf8ToLocale(name).c_str());
510                 break;
511
512         case MPD_ENTITY_TYPE_SONG:
513                 paint_song_row(w, y, width, selected, highlight,
514                                mpd_entity_get_song(entity), nullptr,
515                                song_format);
516                 break;
517
518         case MPD_ENTITY_TYPE_PLAYLIST:
519                 playlist = mpd_entity_get_playlist(entity);
520                 name = g_basename(mpd_playlist_get_path(playlist));
521                 screen_browser_paint_playlist(w, width, selected,
522                                               Utf8ToLocale(name).c_str());
523                 break;
524
525         default:
526                 row_paint_text(w, width, highlight ? COLOR_LIST_BOLD : COLOR_LIST,
527                                selected, "<unknown>");
528         }
529 }
530
531 void
532 FileListPage::Paint() const
533 {
534         lw.Paint(*this);
535 }