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