1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2018 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
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.
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.
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.
20 #include "SearchPage.hxx"
21 #include "PageMeta.hxx"
22 #include "screen_status.hxx"
23 #include "TextListRenderer.hxx"
25 #include "Options.hxx"
26 #include "Bindings.hxx"
27 #include "GlobalBindings.hxx"
28 #include "charset.hxx"
29 #include "mpdclient.hxx"
30 #include "strfsong.hxx"
31 #include "screen_utils.hxx"
32 #include "FileListPage.hxx"
33 #include "filelist.hxx"
34 #include "util/Macros.hxx"
35 #include "util/ScopeExit.hxx"
42 SEARCH_URI = MPD_TAG_COUNT + 100,
46 static constexpr struct {
47 enum mpd_tag_type tag_type;
49 const char *localname;
50 } search_tag[MPD_TAG_COUNT] = {
51 { MPD_TAG_ARTIST, "artist", N_("artist") },
52 { MPD_TAG_ALBUM, "album", N_("album") },
53 { MPD_TAG_TITLE, "title", N_("title") },
54 { MPD_TAG_TRACK, "track", N_("track") },
55 { MPD_TAG_NAME, "name", N_("name") },
56 { MPD_TAG_GENRE, "genre", N_("genre") },
57 { MPD_TAG_DATE, "date", N_("date") },
58 { MPD_TAG_COMPOSER, "composer", N_("composer") },
59 { MPD_TAG_PERFORMER, "performer", N_("performer") },
60 { MPD_TAG_COMMENT, "comment", N_("comment") },
61 { MPD_TAG_COUNT, nullptr, nullptr }
65 search_get_tag_id(const char *name)
67 if (strcasecmp(name, "file") == 0 ||
68 strcasecmp(name, _("file")) == 0)
71 for (unsigned i = 0; search_tag[i].name != nullptr; ++i)
72 if (strcasecmp(search_tag[i].name, name) == 0 ||
73 strcasecmp(search_tag[i].localname, name) == 0)
74 return search_tag[i].tag_type;
80 enum mpd_tag_type table;
84 static constexpr search_type_t mode[] = {
85 { MPD_TAG_TITLE, N_("Title") },
86 { MPD_TAG_ARTIST, N_("Artist") },
87 { MPD_TAG_ALBUM, N_("Album") },
88 { (enum mpd_tag_type)SEARCH_URI, N_("Filename") },
89 { (enum mpd_tag_type)SEARCH_ARTIST_TITLE, N_("Artist + Title") },
90 { MPD_TAG_COUNT, nullptr }
93 static const char *const help_text[] = {
97 "Quick - Enter a string and ncmpc will search according",
98 " to the current search mode (displayed above).",
100 "Advanced - <tag>:<search term> [<tag>:<search term>...]",
101 " Example: artist:radiohead album:pablo honey",
103 " Available tags: artist, album, title, track,",
104 " name, genre, date composer, performer, comment, file",
108 static bool advanced_search_mode = false;
110 class SearchPage final : public FileListPage {
111 History search_history;
115 SearchPage(ScreenManager &_screen, WINDOW *_w, Size size)
116 :FileListPage(_screen, _w, size,
117 !options.search_format.empty()
118 ? options.search_format.c_str()
119 : options.list_format.c_str()) {
120 lw.SetLength(ARRAY_SIZE(help_text));
121 lw.hide_cursor = true;
125 void Clear(bool clear_pattern);
126 void Reload(struct mpdclient &c);
127 void Start(struct mpdclient &c);
130 /* virtual methods from class Page */
131 void Paint() const noexcept override;
132 void Update(struct mpdclient &c, unsigned events) noexcept override;
133 bool OnCommand(struct mpdclient &c, Command cmd) override;
134 const char *GetTitle(char *s, size_t size) const noexcept override;
138 class SearchHelpText final : public ListText {
140 /* virtual methods from class ListText */
141 const char *GetListItemText(char *buffer, size_t size,
142 unsigned idx) const override {
143 assert(idx < ARRAY_SIZE(help_text));
146 snprintf(buffer, size,
148 GetGlobalKeyBindings().GetKeyNames(Command::SCREEN_SEARCH,
155 snprintf(buffer, size,
157 GetGlobalKeyBindings().GetKeyNames(Command::SEARCH_MODE,
159 get_key_description(Command::SEARCH_MODE),
160 gettext(mode[options.search_mode].label));
164 return help_text[idx];
169 SearchPage::Clear(bool clear_pattern)
173 filelist = new FileList();
183 search_simple_query(struct mpd_connection *connection, bool exact_match,
184 int table, const char *local_pattern)
187 const LocaleToUtf8 filter_utf8(local_pattern);
189 if (table == SEARCH_ARTIST_TITLE) {
190 mpd_command_list_begin(connection, false);
192 mpd_search_db_songs(connection, exact_match);
193 mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
195 filter_utf8.c_str());
196 mpd_search_commit(connection);
198 mpd_search_db_songs(connection, exact_match);
199 mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
201 filter_utf8.c_str());
202 mpd_search_commit(connection);
204 mpd_command_list_end(connection);
206 list = filelist_new_recv(connection);
207 list->RemoveDuplicateSongs();
208 } else if (table == SEARCH_URI) {
209 mpd_search_db_songs(connection, exact_match);
210 mpd_search_add_uri_constraint(connection, MPD_OPERATOR_DEFAULT,
211 filter_utf8.c_str());
212 mpd_search_commit(connection);
214 list = filelist_new_recv(connection);
216 mpd_search_db_songs(connection, exact_match);
217 mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
218 (enum mpd_tag_type)table,
219 filter_utf8.c_str());
220 mpd_search_commit(connection);
222 list = filelist_new_recv(connection);
228 /*-----------------------------------------------------------------------
229 * NOTE: This code exists to test a new search ui,
230 * Its ugly and MUST be redesigned before the next release!
231 *-----------------------------------------------------------------------
234 search_advanced_query(struct mpd_connection *connection, const char *query)
236 advanced_search_mode = false;
237 if (strchr(query, ':') == nullptr)
240 char *str = g_strdup(query);
241 AtScopeExit(str) { g_free(str); };
243 static constexpr size_t N = 10;
250 * Replace every : with a '\0' and every space character
251 * before it unless spi = -1, link the resulting strings
252 * to their proper vector.
256 for (size_t i = 0; str[i] != '\0' && n < N; i++) {
266 matchv[n] = str + i + 1;
267 tabv[n] = str + spi + 1;
268 table[n] = search_get_tag_id(tabv[n]);
270 screen_status_printf(_("Bad search tag %s"),
282 /* Get rid of obvious failure case */
283 if (matchv[n - 1][0] == '\0') {
284 screen_status_printf(_("No argument for search tag %s"), tabv[n - 1]);
288 advanced_search_mode = true;
290 /*-----------------------------------------------------------------------
291 * NOTE (again): This code exists to test a new search ui,
292 * Its ugly and MUST be redesigned before the next release!
293 * + the code below should live in mpdclient.c
294 *-----------------------------------------------------------------------
296 /** stupid - but this is just a test...... (fulhack) */
297 mpd_search_db_songs(connection, false);
299 for (size_t i = 0; i < n; i++) {
300 const LocaleToUtf8 value(matchv[i]);
302 if (table[i] == SEARCH_URI)
303 mpd_search_add_uri_constraint(connection,
304 MPD_OPERATOR_DEFAULT,
307 mpd_search_add_tag_constraint(connection,
308 MPD_OPERATOR_DEFAULT,
309 (enum mpd_tag_type)table[i],
313 mpd_search_commit(connection);
314 auto *fl = filelist_new_recv(connection);
315 if (!mpd_response_finish(connection)) {
324 do_search(struct mpdclient *c, const char *query)
326 auto *connection = c->GetConnection();
327 if (connection == nullptr)
330 auto *fl = search_advanced_query(connection, query);
334 if (mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
339 fl = search_simple_query(connection, false,
340 mode[options.search_mode].table,
348 SearchPage::Reload(struct mpdclient &c)
353 lw.hide_cursor = false;
355 filelist = do_search(&c, pattern.c_str());
356 if (filelist == nullptr)
357 filelist = new FileList();
358 lw.SetLength(filelist->size());
360 screen_browser_sync_highlights(filelist, &c.playlist);
366 SearchPage::Start(struct mpdclient &c)
368 if (!c.IsConnected())
373 pattern = screen_readln(_("Search"),
378 if (pattern.empty()) {
386 static std::unique_ptr<Page>
387 screen_search_init(ScreenManager &_screen, WINDOW *w, Size size)
389 return std::make_unique<SearchPage>(_screen, w, size);
393 SearchPage::Paint() const noexcept
396 FileListPage::Paint();
398 lw.Paint(TextListRenderer(SearchHelpText()));
403 SearchPage::GetTitle(char *str, size_t size) const noexcept
405 if (advanced_search_mode && !pattern.empty())
406 snprintf(str, size, "%s '%s'", _("Search"), pattern.c_str());
407 else if (!pattern.empty())
412 gettext(mode[options.search_mode].label));
420 SearchPage::Update(struct mpdclient &c, unsigned events) noexcept
422 if (filelist != nullptr && events & MPD_IDLE_QUEUE) {
423 screen_browser_sync_highlights(filelist, &c.playlist);
429 SearchPage::OnCommand(struct mpdclient &c, Command cmd)
432 case Command::SEARCH_MODE:
433 options.search_mode++;
434 if (mode[options.search_mode].label == nullptr)
435 options.search_mode = 0;
436 screen_status_printf(_("Search mode: %s"),
437 gettext(mode[options.search_mode].label));
440 /* show the new mode in the help text */
442 else if (!advanced_search_mode)
443 /* reload only if the new search mode is going
448 case Command::SCREEN_UPDATE:
452 case Command::SCREEN_SEARCH:
465 if (FileListPage::OnCommand(c, cmd))
471 const PageMeta screen_search = {
474 Command::SCREEN_SEARCH,