Queue: remove FindUri(mpd_song)
[ncmpc-debian.git] / src / FileListPage.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 "FileListPage.hxx"
22 #include "FileBrowserPage.hxx"
23 #include "SongPage.hxx"
24 #include "LyricsPage.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 <glib.h>
41
42 #include <string.h>
43
44 #define BUFSIZE 1024
45
46 #ifndef NCMPC_MINI
47 #define HIGHLIGHT  (0x01)
48 #endif
49
50 FileListPage::~FileListPage()
51 {
52         delete filelist;
53 }
54
55 #ifndef NCMPC_MINI
56
57 /* sync highlight flags with playlist */
58 void
59 screen_browser_sync_highlights(FileList *fl, const MpdQueue *playlist)
60 {
61         for (unsigned i = 0; i < fl->size(); ++i) {
62                 auto &entry = (*fl)[i];
63                 const auto *entity = entry.entity;
64
65                 if (entity != nullptr && mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
66                         const auto *song = mpd_entity_get_song(entity);
67
68                         if (playlist->FindByUri(mpd_song_get_uri(song)) >= 0)
69                                 entry.flags |= HIGHLIGHT;
70                         else
71                                 entry.flags &= ~HIGHLIGHT;
72                 }
73         }
74 }
75
76 #endif
77
78 const char *
79 FileListPage::GetListItemText(char *buffer, size_t size,
80                               unsigned idx) const
81 {
82         assert(filelist != nullptr);
83         assert(idx < filelist->size());
84
85         const auto &entry = (*filelist)[idx];
86         const auto *entity = entry.entity;
87
88         if( entity == nullptr )
89                 return "..";
90
91         if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_DIRECTORY) {
92                 const auto *dir = mpd_entity_get_directory(entity);
93                 const char *name = g_basename(mpd_directory_get_path(dir));
94                 g_strlcpy(buffer, Utf8ToLocale(name).c_str(), size);
95                 return buffer;
96         } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
97                 const auto *song = mpd_entity_get_song(entity);
98
99                 strfsong(buffer, size, options.list_format.c_str(), song);
100                 return buffer;
101         } else if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
102                 const auto *playlist = mpd_entity_get_playlist(entity);
103                 const char *name = g_basename(mpd_playlist_get_path(playlist));
104                 g_strlcpy(buffer, Utf8ToLocale(name).c_str(), size);
105                 return buffer;
106         }
107
108         return "Error: Unknown entry!";
109 }
110
111 static bool
112 load_playlist(struct mpdclient *c, const struct mpd_playlist *playlist)
113 {
114         auto *connection = c->GetConnection();
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                 c->HandleError();
126
127         return true;
128 }
129
130 static bool
131 enqueue_and_play(struct mpdclient *c, FileListEntry *entry)
132 {
133         auto *connection = c->GetConnection();
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.FindIdByUri(mpd_song_get_uri(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                         c->HandleError();
153                         return false;
154                 }
155
156 #ifndef NCMPC_MINI
157                 entry->flags |= HIGHLIGHT;
158 #endif
159                 strfsong(buf, BUFSIZE, options.list_format.c_str(), song);
160                 screen_status_printf(_("Adding \'%s\' to queue"), buf);
161         }
162
163         if (!mpd_run_play_id(connection, id)) {
164                 c->HandleError();
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,
271                                  options.list_format.c_str(), song);
272                         screen_status_printf(_("Adding \'%s\' to queue"), buf);
273                 }
274 #ifndef NCMPC_MINI
275         } else {
276                 /* remove song from playlist */
277                 const auto *song = mpd_entity_get_song(entry->entity);
278                 int idx;
279
280                 entry->flags &= ~HIGHLIGHT;
281
282                 while ((idx = c->playlist.FindByUri(mpd_song_get_uri(song))) >= 0)
283                         mpdclient_cmd_delete(c, idx);
284 #endif
285         }
286
287         return true;
288 }
289
290 bool
291 FileListPage::HandleSelect(struct mpdclient &c)
292 {
293         bool success = false;
294
295         const auto range = lw.GetRange();
296         for (const unsigned i : range) {
297                 auto *entry = GetIndex(i);
298                 if (entry != nullptr && entry->entity != nullptr)
299                         success = browser_select_entry(&c, entry, true);
300         }
301
302         SetDirty();
303
304         return range.end_index == range.start_index + 1 && success;
305 }
306
307 bool
308 FileListPage::HandleAdd(struct mpdclient &c)
309 {
310         bool success = false;
311
312         const auto range = lw.GetRange();
313         for (const unsigned i : range) {
314                 auto *entry = GetIndex(i);
315                 if (entry != nullptr && entry->entity != nullptr)
316                         success = browser_select_entry(&c, entry, false) ||
317                                 success;
318         }
319
320         return range.end_index == range.start_index + 1 && success;
321 }
322
323 void
324 FileListPage::HandleSelectAll(struct mpdclient &c)
325 {
326         if (filelist == nullptr)
327                 return;
328
329         for (unsigned i = 0; i < filelist->size(); ++i) {
330                 auto &entry = (*filelist)[i];
331
332                 if (entry.entity != nullptr)
333                         browser_select_entry(&c, &entry, false);
334         }
335
336         SetDirty();
337 }
338
339 #ifdef HAVE_GETMOUSE
340
341 bool
342 FileListPage::OnMouse(struct mpdclient &c, Point p,
343                       mmask_t bstate)
344 {
345         unsigned prev_selected = lw.selected;
346
347         if (ListPage::OnMouse(c, p, bstate))
348                 return true;
349
350         lw.SetCursor(lw.start + p.y);
351
352         if( bstate & BUTTON1_CLICKED ) {
353                 if (prev_selected == lw.selected)
354                         HandleEnter(c);
355         } else if (bstate & BUTTON3_CLICKED) {
356                 if (prev_selected == lw.selected)
357                         HandleSelect(c);
358         }
359
360         return true;
361 }
362
363 #endif
364
365 bool
366 FileListPage::OnCommand(struct mpdclient &c, command_t cmd)
367 {
368         if (filelist == nullptr)
369                 return false;
370
371         if (ListPage::OnCommand(c, cmd))
372                 return true;
373
374         switch (cmd) {
375 #if defined(ENABLE_SONG_SCREEN) || defined(ENABLE_LYRICS_SCREEN)
376                 const struct mpd_song *song;
377 #endif
378
379         case CMD_LIST_FIND:
380         case CMD_LIST_RFIND:
381         case CMD_LIST_FIND_NEXT:
382         case CMD_LIST_RFIND_NEXT:
383                 screen_find(screen, &lw, cmd, *this);
384                 SetDirty();
385                 return true;
386         case CMD_LIST_JUMP:
387                 screen_jump(screen, &lw, *this, *this);
388                 SetDirty();
389                 return true;
390
391 #ifdef ENABLE_SONG_SCREEN
392         case CMD_SCREEN_SONG:
393                 song = GetSelectedSong();
394                 if (song == nullptr)
395                         return false;
396
397                 screen_song_switch(screen, c, *song);
398                 return true;
399 #endif
400
401 #ifdef ENABLE_LYRICS_SCREEN
402         case CMD_SCREEN_LYRICS:
403                 song = GetSelectedSong();
404                 if (song == nullptr)
405                         return false;
406
407                 screen_lyrics_switch(screen, c, *song, false);
408                 return true;
409 #endif
410         case CMD_SCREEN_SWAP:
411                 screen.Swap(c, GetSelectedSong());
412                 return true;
413
414         default:
415                 break;
416         }
417
418         if (!c.IsConnected())
419                 return false;
420
421         switch (cmd) {
422                 const struct mpd_song *song;
423
424         case CMD_PLAY:
425                 HandleEnter(c);
426                 return true;
427
428         case CMD_SELECT:
429                 if (HandleSelect(c))
430                         lw.HandleCommand(CMD_LIST_NEXT);
431                 SetDirty();
432                 return true;
433
434         case CMD_ADD:
435                 if (HandleAdd(c))
436                         lw.HandleCommand(CMD_LIST_NEXT);
437                 SetDirty();
438                 return true;
439
440         case CMD_SELECT_ALL:
441                 HandleSelectAll(c);
442                 return true;
443
444         case CMD_LOCATE:
445                 song = GetSelectedSong();
446                 if (song == nullptr)
447                         return false;
448
449                 screen_file_goto_song(screen, c, *song);
450                 return true;
451
452         default:
453                 break;
454         }
455
456         return false;
457 }
458
459 void
460 screen_browser_paint_directory(WINDOW *w, unsigned width,
461                                bool selected, const char *name)
462 {
463         row_color(w, COLOR_DIRECTORY, selected);
464
465         waddch(w, '[');
466         waddstr(w, name);
467         waddch(w, ']');
468
469         /* erase the unused space after the text */
470         row_clear_to_eol(w, width, selected);
471 }
472
473 static void
474 screen_browser_paint_playlist(WINDOW *w, unsigned width,
475                               bool selected, const char *name)
476 {
477         row_paint_text(w, width, COLOR_PLAYLIST, selected, name);
478 }
479
480 void
481 FileListPage::PaintListItem(WINDOW *w, unsigned i,
482                             unsigned y, unsigned width,
483                             bool selected) const
484 {
485         assert(filelist != nullptr);
486         assert(i < filelist->size());
487
488         const auto &entry = (*filelist)[i];
489         const struct mpd_entity *entity = entry.entity;
490         if (entity == nullptr) {
491                 screen_browser_paint_directory(w, width, selected, "..");
492                 return;
493         }
494
495 #ifndef NCMPC_MINI
496         const bool highlight = (entry.flags & HIGHLIGHT) != 0;
497 #else
498         const bool highlight = false;
499 #endif
500
501         switch (mpd_entity_get_type(entity)) {
502                 const struct mpd_directory *directory;
503                 const struct mpd_playlist *playlist;
504                 const char *name;
505
506         case MPD_ENTITY_TYPE_DIRECTORY:
507                 directory = mpd_entity_get_directory(entity);
508                 name = g_basename(mpd_directory_get_path(directory));
509                 screen_browser_paint_directory(w, width, selected,
510                                                Utf8ToLocale(name).c_str());
511                 break;
512
513         case MPD_ENTITY_TYPE_SONG:
514                 paint_song_row(w, y, width, selected, highlight,
515                                mpd_entity_get_song(entity), nullptr,
516                                song_format);
517                 break;
518
519         case MPD_ENTITY_TYPE_PLAYLIST:
520                 playlist = mpd_entity_get_playlist(entity);
521                 name = g_basename(mpd_playlist_get_path(playlist));
522                 screen_browser_paint_playlist(w, width, selected,
523                                               Utf8ToLocale(name).c_str());
524                 break;
525
526         default:
527                 row_paint_text(w, width, highlight ? COLOR_LIST_BOLD : COLOR_LIST,
528                                selected, "<unknown>");
529         }
530 }
531
532 void
533 FileListPage::Paint() const
534 {
535         lw.Paint(*this);
536 }