screen_artist: split into three Page implementations
[ncmpc-debian.git] / src / AlbumListPage.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 "AlbumListPage.hxx"
21 #include "screen_interface.hxx"
22 #include "screen_status.hxx"
23 #include "screen_find.hxx"
24 #include "screen_browser.hxx"
25 #include "screen.hxx"
26 #include "i18n.h"
27 #include "charset.hxx"
28 #include "mpdclient.hxx"
29 #include "filelist.hxx"
30
31 #include <glib.h>
32
33 #include <algorithm>
34
35 #include <assert.h>
36 #include <string.h>
37
38 #define BUFSIZE 1024
39
40 gcc_pure
41 static bool
42 CompareUTF8(const std::string &a, const std::string &b)
43 {
44         char *key1 = g_utf8_collate_key(a.c_str(), -1);
45         char *key2 = g_utf8_collate_key(b.c_str(), -1);
46         int n = strcmp(key1,key2);
47         g_free(key1);
48         g_free(key2);
49         return n < 0;
50 }
51
52 /* list_window callback */
53 static const char *
54 album_lw_callback(unsigned idx, void *data)
55 {
56         const auto &list = *(const std::vector<std::string> *)data;
57
58         assert(idx < list.size());
59
60         const char *str_utf8 = list[idx].c_str();
61         char *str = utf8_to_locale(str_utf8);
62
63         static char buf[BUFSIZE];
64         g_strlcpy(buf, str, sizeof(buf));
65         g_free(str);
66
67         return buf;
68 }
69
70 /* list_window callback */
71 static const char *
72 AlbumListCallback(unsigned idx, void *data)
73 {
74         const auto &list = *(const std::vector<std::string> *)data;
75
76         if (idx == 0)
77                 return "..";
78         else if (idx == list.size() + 1)
79                 return _("All tracks");
80
81         --idx;
82
83         return album_lw_callback(idx, data);
84 }
85
86 static void
87 recv_tag_values(struct mpd_connection *connection, enum mpd_tag_type tag,
88                 std::vector<std::string> &list)
89 {
90         struct mpd_pair *pair;
91
92         while ((pair = mpd_recv_pair_tag(connection, tag)) != nullptr) {
93                 list.emplace_back(pair->value);
94                 mpd_return_pair(connection, pair);
95         }
96 }
97
98 void
99 AlbumListPage::LoadAlbumList(struct mpdclient &c)
100 {
101         struct mpd_connection *connection = mpdclient_get_connection(&c);
102
103         album_list.clear();
104
105         if (connection != nullptr) {
106                 mpd_search_db_tags(connection, MPD_TAG_ALBUM);
107                 mpd_search_add_tag_constraint(connection,
108                                               MPD_OPERATOR_DEFAULT,
109                                               MPD_TAG_ARTIST,
110                                               artist.c_str());
111                 mpd_search_commit(connection);
112
113                 recv_tag_values(connection, MPD_TAG_ALBUM, album_list);
114
115                 mpdclient_finish_command(&c);
116         }
117
118         /* sort list */
119         std::sort(album_list.begin(), album_list.end(), CompareUTF8);
120         lw.SetLength(album_list.size() + 2);
121 }
122
123 void
124 AlbumListPage::Reload(struct mpdclient &c)
125 {
126         LoadAlbumList(c);
127 }
128
129 /**
130  * Paint one item in the album list.  There are two virtual items
131  * inserted: at the beginning, there's the special item ".." to go to
132  * the parent directory, and at the end, there's the item "All tracks"
133  * to view the tracks of all albums.
134  */
135 static void
136 paint_album_callback(WINDOW *w, unsigned i,
137                      gcc_unused unsigned y, unsigned width,
138                      bool selected, const void *data)
139 {
140         const auto &list = *(const std::vector<std::string> *)data;
141         const char *p;
142         char *q = nullptr;
143
144         if (i == 0)
145                 p = "..";
146         else if (i == list.size() + 1)
147                 p = _("All tracks");
148         else
149                 p = q = utf8_to_locale(list[i - 1].c_str());
150
151         screen_browser_paint_directory(w, width, selected, p);
152         g_free(q);
153 }
154
155 void
156 AlbumListPage::Paint() const
157 {
158         lw.Paint(paint_album_callback, &album_list);
159 }
160
161 const char *
162 AlbumListPage::GetTitle(char *str, size_t size) const
163 {
164         if (artist.empty())
165                 return _("Albums");
166
167         char *s1 = utf8_to_locale(artist.c_str());
168         g_snprintf(str, size, _("Albums of artist: %s"), s1);
169         g_free(s1);
170         return str;
171 }
172
173 void
174 AlbumListPage::Update(struct mpdclient &c, unsigned events)
175 {
176         if (events & MPD_IDLE_DATABASE) {
177                 /* the db has changed -> update the list */
178                 Reload(c);
179                 SetDirty();
180         }
181 }
182
183 /* add_query - Add all songs satisfying specified criteria.
184    _artist is actually only used in the ALBUM case to distinguish albums with
185    the same name from different artists. */
186 static void
187 add_query(struct mpdclient *c, enum mpd_tag_type table, const char *_filter,
188           const char *_artist)
189 {
190         struct mpd_connection *connection = mpdclient_get_connection(c);
191
192         assert(_filter != nullptr);
193
194         if (connection == nullptr)
195                 return;
196
197         char *str = utf8_to_locale(_filter);
198         if (table == MPD_TAG_ALBUM)
199                 screen_status_printf(_("Adding album %s..."), str);
200         else
201                 screen_status_printf(_("Adding %s..."), str);
202         g_free(str);
203
204         mpd_search_db_songs(connection, true);
205         mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
206                                       table, _filter);
207         if (table == MPD_TAG_ALBUM)
208                 mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
209                                               MPD_TAG_ARTIST, _artist);
210         mpd_search_commit(connection);
211
212         auto *addlist = filelist_new_recv(connection);
213
214         if (mpdclient_finish_command(c))
215                 mpdclient_filelist_add_all(c, addlist);
216
217         delete addlist;
218 }
219
220 bool
221 AlbumListPage::OnCommand(struct mpdclient &c, command_t cmd)
222 {
223         switch(cmd) {
224                 const char *selected;
225
226         case CMD_PLAY:
227                 if (lw.selected == 0) {
228                         /* handle ".." */
229                         screen.OnCommand(c, CMD_GO_PARENT_DIRECTORY);
230                         return true;
231                 }
232
233                 break;
234
235         case CMD_SELECT:
236         case CMD_ADD:
237                 for (const unsigned i : lw.GetRange()) {
238                         if(i == album_list.size() + 1)
239                                 add_query(&c, MPD_TAG_ARTIST, artist.c_str(),
240                                           nullptr);
241                         else if (i > 0) {
242                                 selected = album_list[lw.selected - 1].c_str();
243                                 add_query(&c, MPD_TAG_ALBUM, selected,
244                                           artist.c_str());
245                                 cmd = CMD_LIST_NEXT; /* continue and select next item... */
246                         }
247                 }
248                 break;
249
250                 /* continue and update... */
251         case CMD_SCREEN_UPDATE:
252                 Reload(c);
253                 return false;
254
255         case CMD_LIST_FIND:
256         case CMD_LIST_RFIND:
257         case CMD_LIST_FIND_NEXT:
258         case CMD_LIST_RFIND_NEXT:
259                 screen_find(screen, &lw, cmd,
260                             AlbumListCallback, &album_list);
261                 SetDirty();
262                 return true;
263
264         case CMD_LIST_JUMP:
265                 screen_jump(screen, &lw,
266                             AlbumListCallback, &album_list,
267                             paint_album_callback, &album_list);
268                 SetDirty();
269                 return true;
270
271         default:
272                 break;
273         }
274
275         if (lw.HandleCommand(cmd)) {
276                 SetDirty();
277                 return true;
278         }
279
280         return false;
281 }