plugin: convert plugin_fd_add() to method
[ncmpc-debian.git] / src / LibraryPage.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2019 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 "LibraryPage.hxx"
21 #include "TagListPage.hxx"
22 #include "PageMeta.hxx"
23 #include "FileListPage.hxx"
24 #include "Command.hxx"
25 #include "ProxyPage.hxx"
26 #include "i18n.h"
27 #include "charset.hxx"
28 #include "mpdclient.hxx"
29 #include "filelist.hxx"
30 #include "Options.hxx"
31
32 #include <list>
33 #include <string>
34 #include <vector>
35
36 #include <assert.h>
37 #include <string.h>
38
39 gcc_const
40 static const char *
41 GetTagPlural(enum mpd_tag_type tag) noexcept
42 {
43         switch (tag) {
44         case MPD_TAG_ARTIST:
45                 return _("Artists");
46
47         case MPD_TAG_ALBUM:
48                 return _("Albums");
49
50         default:
51                 return mpd_tag_name(tag);
52         }
53 }
54
55 static const char *
56 MakePageTitle(char *buffer, size_t size, const char *prefix,
57               const TagFilter &filter)
58 {
59         if (filter.empty())
60                 return prefix;
61
62         snprintf(buffer, size, "%s: %s", prefix,
63                  Utf8ToLocale(ToString(filter).c_str()).c_str());
64         return buffer;
65 }
66
67 class SongListPage final : public FileListPage {
68         Page *const parent;
69
70         TagFilter filter;
71
72 public:
73         SongListPage(ScreenManager &_screen, Page *_parent,
74                      WINDOW *_w, Size size) noexcept
75                 :FileListPage(_screen, _w, size,
76                               options.list_format.c_str()),
77                  parent(_parent) {}
78
79         const auto &GetFilter() const noexcept {
80                 return filter;
81         }
82
83         template<typename F>
84         void SetFilter(F &&_filter) noexcept {
85                 filter = std::forward<F>(_filter);
86                 AddPendingEvents(~0u);
87         }
88
89         void LoadSongList(struct mpdclient &c);
90
91         /* virtual methods from class Page */
92         void Update(struct mpdclient &c, unsigned events) noexcept override;
93         bool OnCommand(struct mpdclient &c, Command cmd) override;
94         const char *GetTitle(char *s, size_t size) const noexcept override;
95 };
96
97 void
98 SongListPage::Update(struct mpdclient &c, unsigned events) noexcept
99 {
100         if (events & MPD_IDLE_DATABASE) {
101                 LoadSongList(c);
102         }
103 }
104
105 class ArtistBrowserPage final : public ProxyPage {
106         std::list<TagListPage> tag_list_pages;
107         std::list<TagListPage>::iterator current_tag_list_page;
108
109         SongListPage song_list_page;
110
111 public:
112         ArtistBrowserPage(ScreenManager &_screen, WINDOW *_w,
113                           Size size)
114                 :ProxyPage(_w),
115                  song_list_page(_screen, this,
116                                 _w, size) {
117
118                 bool first = true;
119                 for (const auto &tag : options.library_page_tags) {
120                         tag_list_pages.emplace_back(_screen,
121                                                     first ? nullptr : this,
122                                                     tag,
123                                                     first ? nullptr : _("All"),
124                                                     _w, size);
125                         first = false;
126                 }
127         }
128
129 private:
130         void OpenTagPage(struct mpdclient &c,
131                          TagFilter &&filter) noexcept;
132         void OpenSongList(struct mpdclient &c, TagFilter &&filter);
133
134 public:
135         /* virtual methods from class Page */
136         void OnOpen(struct mpdclient &c) noexcept override;
137         void Update(struct mpdclient &c, unsigned events) noexcept override;
138         bool OnCommand(struct mpdclient &c, Command cmd) override;
139 };
140
141 void
142 SongListPage::LoadSongList(struct mpdclient &c)
143 {
144         auto *connection = c.GetConnection();
145
146         delete filelist;
147
148         filelist = new FileList();
149         /* add a dummy entry for ".." */
150         filelist->emplace_back(nullptr);
151
152         if (connection != nullptr) {
153                 mpd_search_db_songs(connection, true);
154                 AddConstraints(connection, filter);
155                 mpd_search_commit(connection);
156
157                 filelist->Receive(*connection);
158
159                 c.FinishCommand();
160         }
161
162         /* fix highlights */
163         screen_browser_sync_highlights(filelist, &c.playlist);
164         lw.SetLength(filelist->size());
165 }
166
167 void
168 ArtistBrowserPage::OpenTagPage(struct mpdclient &c,
169                                TagFilter &&filter) noexcept
170 {
171         auto page = current_tag_list_page;
172
173         page->SetFilter(std::move(filter));
174
175         char buffer[64];
176         page->SetTitle(MakePageTitle(buffer, sizeof(buffer),
177                                      _("Albums"),
178                                      page->GetFilter()));
179
180         SetCurrentPage(c, &*page);
181 }
182
183 void
184 ArtistBrowserPage::OpenSongList(struct mpdclient &c, TagFilter &&filter)
185 {
186         song_list_page.SetFilter(std::move(filter));
187         SetCurrentPage(c, &song_list_page);
188 }
189
190 static std::unique_ptr<Page>
191 InitLibraryPage(ScreenManager &_screen, WINDOW *w, Size size)
192 {
193         return std::make_unique<ArtistBrowserPage>(_screen, w, size);
194 }
195
196 const char *
197 SongListPage::GetTitle(char *str, size_t size) const noexcept
198 {
199         return MakePageTitle(str, size, _("Songs"), filter);
200 }
201
202 bool
203 SongListPage::OnCommand(struct mpdclient &c, Command cmd)
204 {
205         switch(cmd) {
206         case Command::PLAY:
207                 if (lw.GetCursorIndex() == 0 && parent != nullptr)
208                         /* handle ".." */
209                         return parent->OnCommand(c, Command::GO_PARENT_DIRECTORY);
210
211                 break;
212
213                 /* continue and update... */
214         case Command::SCREEN_UPDATE:
215                 LoadSongList(c);
216                 return false;
217
218         default:
219                 break;
220         }
221
222         return FileListPage::OnCommand(c, cmd);
223 }
224
225 void
226 ArtistBrowserPage::OnOpen(struct mpdclient &c) noexcept
227 {
228         ProxyPage::OnOpen(c);
229
230         if (GetCurrentPage() == nullptr) {
231                 current_tag_list_page = tag_list_pages.begin();
232                 assert(current_tag_list_page != tag_list_pages.end());
233
234                 current_tag_list_page->SetTitle(GetTagPlural(current_tag_list_page->GetTag()));
235                 SetCurrentPage(c, &*current_tag_list_page);
236         }
237 }
238
239 void
240 ArtistBrowserPage::Update(struct mpdclient &c, unsigned events) noexcept
241 {
242         for (auto &i : tag_list_pages)
243                 i.AddPendingEvents(events);
244         song_list_page.AddPendingEvents(events);
245
246         ProxyPage::Update(c, events);
247 }
248
249 bool
250 ArtistBrowserPage::OnCommand(struct mpdclient &c, Command cmd)
251 {
252         if (ProxyPage::OnCommand(c, cmd))
253                 return true;
254
255         switch (cmd) {
256         case Command::PLAY:
257                 if (current_tag_list_page != tag_list_pages.end()) {
258                         auto filter = current_tag_list_page->MakeCursorFilter();
259                         if (filter.empty())
260                                 break;
261
262                         ++current_tag_list_page;
263
264                         if (current_tag_list_page != tag_list_pages.end())
265                                 OpenTagPage(c,  std::move(filter));
266                         else
267                                 OpenSongList(c, std::move(filter));
268                         return true;
269                 }
270
271                 break;
272
273         case Command::GO_ROOT_DIRECTORY:
274                 if (GetCurrentPage() != &tag_list_pages.front()) {
275                         current_tag_list_page = tag_list_pages.begin();
276                         SetCurrentPage(c, &*current_tag_list_page);
277                         return true;
278                 }
279
280                 break;
281
282         case Command::GO_PARENT_DIRECTORY:
283                 if (current_tag_list_page != tag_list_pages.begin()) {
284                         --current_tag_list_page;
285                         SetCurrentPage(c, &*current_tag_list_page);
286                         return true;
287                 }
288
289                 break;
290
291         default:
292                 break;
293         }
294
295         return false;
296 }
297
298 const PageMeta library_page = {
299         "library",
300         N_("Library"),
301         Command::LIBRARY_PAGE,
302         InitLibraryPage,
303 };