Fix warnings when compiling with GCC 7
[ncmpcpp.git] / src / screens / search_engine.cpp
blobc4d4a1a88f4ff48abe9b7f01543b806b6a1c886a
1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <array>
22 #include <boost/range/detail/any_iterator.hpp>
23 #include <iomanip>
25 #include "curses/menu_impl.h"
26 #include "display.h"
27 #include "global.h"
28 #include "helpers.h"
29 #include "screens/playlist.h"
30 #include "screens/search_engine.h"
31 #include "settings.h"
32 #include "status.h"
33 #include "statusbar.h"
34 #include "format_impl.h"
35 #include "helpers/song_iterator_maker.h"
36 #include "utility/comparators.h"
37 #include "title.h"
38 #include "screens/screen_switcher.h"
40 using Global::MainHeight;
41 using Global::MainStartY;
43 namespace ph = std::placeholders;
45 SearchEngine *mySearcher;
47 namespace {
49 /*const std::array<const std::string, 11> constraintsNames = {{
50 "Any",
51 "Artist",
52 "Album Artist",
53 "Title",
54 "Album",
55 "Filename",
56 "Composer",
57 "Performer",
58 "Genre",
59 "Date",
60 "Comment"
61 }};
63 const std::array<const char *, 3> searchModes = {{
64 "Match if tag contains searched phrase (no regexes)",
65 "Match if tag contains searched phrase (regexes supported)",
66 "Match only if both values are the same"
67 }};
69 namespace pos {
70 const size_t searchIn = constraintsNames.size()-1+1+1; // separated
71 const size_t searchMode = searchIn+1;
72 const size_t search = searchMode+1+1; // separated
73 const size_t reset = search+1;
74 }*/
76 std::string SEItemToString(const SEItem &ei);
77 bool SEItemEntryMatcher(const Regex::Regex &rx,
78 const NC::Menu<SEItem>::Item &item,
79 bool filter);
83 template <>
84 struct SongPropertiesExtractor<SEItem>
86 template <typename ItemT>
87 auto &operator()(ItemT &item) const
89 auto s = !item.isSeparator() && item.value().isSong()
90 ? &item.value().song()
91 : nullptr;
92 return m_cache.assign(&item.properties(), s);
95 private:
96 mutable SongProperties m_cache;
99 SongIterator SearchEngineWindow::currentS()
101 return makeSongIterator(current());
104 ConstSongIterator SearchEngineWindow::currentS() const
106 return makeConstSongIterator(current());
109 SongIterator SearchEngineWindow::beginS()
111 return makeSongIterator(begin());
114 ConstSongIterator SearchEngineWindow::beginS() const
116 return makeConstSongIterator(begin());
119 SongIterator SearchEngineWindow::endS()
121 return makeSongIterator(end());
124 ConstSongIterator SearchEngineWindow::endS() const
126 return makeConstSongIterator(end());
129 std::vector<MPD::Song> SearchEngineWindow::getSelectedSongs()
131 std::vector<MPD::Song> result;
132 for (auto &item : *this)
134 if (item.isSelected())
136 assert(item.value().isSong());
137 result.push_back(item.value().song());
140 // If no item is selected, add the current one if it's a song.
141 if (result.empty() && !empty() && current()->value().isSong())
142 result.push_back(current()->value().song());
143 return result;
146 /**********************************************************************/
148 const char *SearchEngine::ConstraintsNames[] =
150 "Any",
151 "Artist",
152 "Album Artist",
153 "Title",
154 "Album",
155 "Filename",
156 "Composer",
157 "Performer",
158 "Genre",
159 "Date",
160 "Comment"
163 const char *SearchEngine::SearchModes[] =
165 "Match if tag contains searched phrase (no regexes)",
166 "Match if tag contains searched phrase (regexes supported)",
167 "Match only if both values are the same",
171 size_t SearchEngine::StaticOptions = 20;
172 size_t SearchEngine::ResetButton = 16;
173 size_t SearchEngine::SearchButton = 15;
175 SearchEngine::SearchEngine()
176 : Screen(NC::Menu<SEItem>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
178 setHighlightFixes(w);
179 w.cyclicScrolling(Config.use_cyclic_scrolling);
180 w.centeredCursor(Config.centered_cursor);
181 w.setItemDisplayer(std::bind(Display::SEItems, ph::_1, std::cref(w)));
182 w.setSelectedPrefix(Config.selected_item_prefix);
183 w.setSelectedSuffix(Config.selected_item_suffix);
184 SearchMode = &SearchModes[Config.search_engine_default_search_mode];
187 void SearchEngine::resize()
189 size_t x_offset, width;
190 getWindowResizeParams(x_offset, width);
191 w.resize(width, MainHeight);
192 w.moveTo(x_offset, MainStartY);
193 switch (Config.search_engine_display_mode)
195 case DisplayMode::Columns:
196 if (Config.titles_visibility)
197 w.setTitle(Display::Columns(w.getWidth()));
198 break;
199 case DisplayMode::Classic:
200 w.setTitle("");
201 break;
203 hasToBeResized = 0;
206 void SearchEngine::switchTo()
208 SwitchTo::execute(this);
209 if (w.empty())
210 Prepare();
211 drawHeader();
214 std::wstring SearchEngine::title()
216 return L"Search engine";
219 void SearchEngine::mouseButtonPressed(MEVENT me)
221 if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
222 return;
223 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
225 if (!w.Goto(me.y))
226 return;
227 w.refresh();
228 if ((me.bstate & BUTTON3_PRESSED)
229 && w.choice() < StaticOptions)
230 runAction();
231 else if (w.choice() >= StaticOptions)
233 bool play = me.bstate & BUTTON3_PRESSED;
234 addItemToPlaylist(play);
237 else
238 Screen<WindowType>::mouseButtonPressed(me);
241 /***********************************************************************/
243 bool SearchEngine::allowsSearching()
245 ScopedUnfilteredMenu<SEItem> sunfilter(ReapplyFilter::Yes, w);
246 return w.rbegin()->value().isSong();
249 const std::string &SearchEngine::searchConstraint()
251 return m_search_predicate.constraint();
254 void SearchEngine::setSearchConstraint(const std::string &constraint)
256 m_search_predicate = Regex::ItemFilter<SEItem>(
257 constraint,
258 Config.regex_type,
259 std::bind(SEItemEntryMatcher, ph::_1, ph::_2, false));
262 void SearchEngine::clearSearchConstraint()
264 m_search_predicate.clear();
267 bool SearchEngine::search(SearchDirection direction, bool wrap, bool skip_current)
269 return ::search(w, m_search_predicate, direction, wrap, skip_current);
272 /***********************************************************************/
274 bool SearchEngine::allowsFiltering()
276 return allowsSearching();
279 std::string SearchEngine::currentFilter()
281 std::string result;
282 if (auto pred = w.filterPredicate<Regex::ItemFilter<SEItem>>())
283 result = pred->constraint();
284 return result;
287 void SearchEngine::applyFilter(const std::string &constraint)
289 if (!constraint.empty())
291 w.applyFilter(Regex::ItemFilter<SEItem>(
292 constraint,
293 Config.regex_type,
294 std::bind(SEItemEntryMatcher, ph::_1, ph::_2, true)));
296 else
297 w.clearFilter();
300 /***********************************************************************/
302 bool SearchEngine::actionRunnable()
304 return !w.empty() && !w.current()->value().isSong();
307 void SearchEngine::runAction()
309 size_t option = w.choice();
310 if (option > ConstraintsNumber && option < SearchButton)
311 w.current()->value().buffer().clear();
313 if (option < ConstraintsNumber)
315 Statusbar::ScopedLock slock;
316 std::string constraint = ConstraintsNames[option];
317 Statusbar::put() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
318 itsConstraints[option] = Global::wFooter->prompt(itsConstraints[option]);
319 w.current()->value().buffer().clear();
320 constraint.resize(13, ' ');
321 w.current()->value().buffer() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
322 ShowTag(w.current()->value().buffer(), itsConstraints[option]);
324 else if (option == ConstraintsNumber+1)
326 Config.search_in_db = !Config.search_in_db;
327 w.current()->value().buffer() << NC::Format::Bold << "Search in:" << NC::Format::NoBold << ' ' << (Config.search_in_db ? "Database" : "Current playlist");
329 else if (option == ConstraintsNumber+2)
331 if (!*++SearchMode)
332 SearchMode = &SearchModes[0];
333 w.current()->value().buffer() << NC::Format::Bold << "Search mode:" << NC::Format::NoBold << ' ' << *SearchMode;
335 else if (option == SearchButton)
337 w.clearFilter();
338 Statusbar::print("Searching...");
339 if (w.size() > StaticOptions)
340 Prepare();
341 Search();
342 if (w.rbegin()->value().isSong())
344 if (Config.search_engine_display_mode == DisplayMode::Columns)
345 w.setTitle(Config.titles_visibility ? Display::Columns(w.getWidth()) : "");
346 size_t found = w.size()-SearchEngine::StaticOptions;
347 found += 3; // don't count options inserted below
348 w.insertSeparator(ResetButton+1);
349 w.insertItem(ResetButton+2, SEItem(), NC::List::Properties::Inactive);
350 w.at(ResetButton+2).value().mkBuffer()
351 << NC::Format::Bold
352 << Config.color1
353 << "Search results: "
354 << NC::FormattedColor::End<>(Config.color1)
355 << Config.color2
356 << "Found " << found << (found > 1 ? " songs" : " song")
357 << NC::FormattedColor::End<>(Config.color2)
358 << NC::Format::NoBold;
359 w.insertSeparator(ResetButton+3);
360 Statusbar::print("Searching finished");
361 if (Config.block_search_constraints_change)
362 for (size_t i = 0; i < StaticOptions-4; ++i)
363 w.at(i).setInactive(true);
364 w.scroll(NC::Scroll::Down);
365 w.scroll(NC::Scroll::Down);
367 else
368 Statusbar::print("No results found");
370 else if (option == ResetButton)
372 reset();
374 else
375 addSongToPlaylist(w.current()->value().song(), true);
378 /***********************************************************************/
380 bool SearchEngine::itemAvailable()
382 return !w.empty() && w.current()->value().isSong();
385 bool SearchEngine::addItemToPlaylist(bool play)
387 return addSongToPlaylist(w.current()->value().song(), play);
390 std::vector<MPD::Song> SearchEngine::getSelectedSongs()
392 return w.getSelectedSongs();
395 /***********************************************************************/
397 void SearchEngine::Prepare()
399 w.setTitle("");
400 w.clear();
401 w.resizeList(StaticOptions-3);
403 for (auto &item : w)
404 item.setSelectable(false);
406 w.at(ConstraintsNumber).setSeparator(true);
407 w.at(SearchButton-1).setSeparator(true);
409 for (size_t i = 0; i < ConstraintsNumber; ++i)
411 std::string constraint = ConstraintsNames[i];
412 constraint.resize(13, ' ');
413 w[i].value().mkBuffer() << NC::Format::Bold << constraint << NC::Format::NoBold << ": ";
414 ShowTag(w[i].value().buffer(), itsConstraints[i]);
417 w.at(ConstraintsNumber+1).value().mkBuffer() << NC::Format::Bold << "Search in:" << NC::Format::NoBold << ' ' << (Config.search_in_db ? "Database" : "Current playlist");
418 w.at(ConstraintsNumber+2).value().mkBuffer() << NC::Format::Bold << "Search mode:" << NC::Format::NoBold << ' ' << *SearchMode;
420 w.at(SearchButton).value().mkBuffer() << "Search";
421 w.at(ResetButton).value().mkBuffer() << "Reset";
424 void SearchEngine::reset()
426 for (size_t i = 0; i < ConstraintsNumber; ++i)
427 itsConstraints[i].clear();
428 w.clearFilter();
429 w.reset();
430 Prepare();
431 Statusbar::print("Search state reset");
434 void SearchEngine::Search()
436 bool constraints_empty = 1;
437 for (size_t i = 0; i < ConstraintsNumber; ++i)
439 if (!itsConstraints[i].empty())
441 constraints_empty = 0;
442 break;
445 if (constraints_empty)
446 return;
448 if (Config.search_in_db && (SearchMode == &SearchModes[0] || SearchMode == &SearchModes[2])) // use built-in mpd searching
450 Mpd.StartSearch(SearchMode == &SearchModes[2]);
451 if (!itsConstraints[0].empty())
452 Mpd.AddSearchAny(itsConstraints[0]);
453 if (!itsConstraints[1].empty())
454 Mpd.AddSearch(MPD_TAG_ARTIST, itsConstraints[1]);
455 if (!itsConstraints[2].empty())
456 Mpd.AddSearch(MPD_TAG_ALBUM_ARTIST, itsConstraints[2]);
457 if (!itsConstraints[3].empty())
458 Mpd.AddSearch(MPD_TAG_TITLE, itsConstraints[3]);
459 if (!itsConstraints[4].empty())
460 Mpd.AddSearch(MPD_TAG_ALBUM, itsConstraints[4]);
461 if (!itsConstraints[5].empty())
462 Mpd.AddSearchURI(itsConstraints[5]);
463 if (!itsConstraints[6].empty())
464 Mpd.AddSearch(MPD_TAG_COMPOSER, itsConstraints[6]);
465 if (!itsConstraints[7].empty())
466 Mpd.AddSearch(MPD_TAG_PERFORMER, itsConstraints[7]);
467 if (!itsConstraints[8].empty())
468 Mpd.AddSearch(MPD_TAG_GENRE, itsConstraints[8]);
469 if (!itsConstraints[9].empty())
470 Mpd.AddSearch(MPD_TAG_DATE, itsConstraints[9]);
471 if (!itsConstraints[10].empty())
472 Mpd.AddSearch(MPD_TAG_COMMENT, itsConstraints[10]);
473 for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
474 w.addItem(std::move(*s));
475 return;
478 Regex::Regex rx[ConstraintsNumber];
479 if (SearchMode != &SearchModes[2]) // match to pattern
481 for (size_t i = 0; i < ConstraintsNumber; ++i)
483 if (!itsConstraints[i].empty())
487 rx[i] = Regex::make(itsConstraints[i], Config.regex_type);
489 catch (boost::bad_expression &) { }
494 typedef boost::range_detail::any_iterator<
495 const MPD::Song,
496 boost::single_pass_traversal_tag,
497 const MPD::Song &,
498 std::ptrdiff_t
499 > input_song_iterator;
500 input_song_iterator s, end;
501 if (Config.search_in_db)
503 s = input_song_iterator(getDatabaseIterator(Mpd));
504 end = input_song_iterator(MPD::SongIterator());
506 else
508 s = input_song_iterator(myPlaylist->main().beginV());
509 end = input_song_iterator(myPlaylist->main().endV());
512 LocaleStringComparison cmp(std::locale(), Config.ignore_leading_the);
513 for (; s != end; ++s)
515 bool any_found = true, found = true;
517 if (SearchMode != &SearchModes[2]) // match to pattern
519 if (!rx[0].empty())
520 any_found =
521 Regex::search(s->getArtist(), rx[0], Config.ignore_diacritics)
522 || Regex::search(s->getAlbumArtist(), rx[0], Config.ignore_diacritics)
523 || Regex::search(s->getTitle(), rx[0], Config.ignore_diacritics)
524 || Regex::search(s->getAlbum(), rx[0], Config.ignore_diacritics)
525 || Regex::search(s->getName(), rx[0], Config.ignore_diacritics)
526 || Regex::search(s->getComposer(), rx[0], Config.ignore_diacritics)
527 || Regex::search(s->getPerformer(), rx[0], Config.ignore_diacritics)
528 || Regex::search(s->getGenre(), rx[0], Config.ignore_diacritics)
529 || Regex::search(s->getDate(), rx[0], Config.ignore_diacritics)
530 || Regex::search(s->getComment(), rx[0], Config.ignore_diacritics);
531 if (found && !rx[1].empty())
532 found = Regex::search(s->getArtist(), rx[1], Config.ignore_diacritics);
533 if (found && !rx[2].empty())
534 found = Regex::search(s->getAlbumArtist(), rx[2], Config.ignore_diacritics);
535 if (found && !rx[3].empty())
536 found = Regex::search(s->getTitle(), rx[3], Config.ignore_diacritics);
537 if (found && !rx[4].empty())
538 found = Regex::search(s->getAlbum(), rx[4], Config.ignore_diacritics);
539 if (found && !rx[5].empty())
540 found = Regex::search(s->getName(), rx[5], Config.ignore_diacritics);
541 if (found && !rx[6].empty())
542 found = Regex::search(s->getComposer(), rx[6], Config.ignore_diacritics);
543 if (found && !rx[7].empty())
544 found = Regex::search(s->getPerformer(), rx[7], Config.ignore_diacritics);
545 if (found && !rx[8].empty())
546 found = Regex::search(s->getGenre(), rx[8], Config.ignore_diacritics);
547 if (found && !rx[9].empty())
548 found = Regex::search(s->getDate(), rx[9], Config.ignore_diacritics);
549 if (found && !rx[10].empty())
550 found = Regex::search(s->getComment(), rx[10], Config.ignore_diacritics);
552 else // match only if values are equal
554 if (!itsConstraints[0].empty())
555 any_found =
556 !cmp(s->getArtist(), itsConstraints[0])
557 || !cmp(s->getAlbumArtist(), itsConstraints[0])
558 || !cmp(s->getTitle(), itsConstraints[0])
559 || !cmp(s->getAlbum(), itsConstraints[0])
560 || !cmp(s->getName(), itsConstraints[0])
561 || !cmp(s->getComposer(), itsConstraints[0])
562 || !cmp(s->getPerformer(), itsConstraints[0])
563 || !cmp(s->getGenre(), itsConstraints[0])
564 || !cmp(s->getDate(), itsConstraints[0])
565 || !cmp(s->getComment(), itsConstraints[0]);
567 if (found && !itsConstraints[1].empty())
568 found = !cmp(s->getArtist(), itsConstraints[1]);
569 if (found && !itsConstraints[2].empty())
570 found = !cmp(s->getAlbumArtist(), itsConstraints[2]);
571 if (found && !itsConstraints[3].empty())
572 found = !cmp(s->getTitle(), itsConstraints[3]);
573 if (found && !itsConstraints[4].empty())
574 found = !cmp(s->getAlbum(), itsConstraints[4]);
575 if (found && !itsConstraints[5].empty())
576 found = !cmp(s->getName(), itsConstraints[5]);
577 if (found && !itsConstraints[6].empty())
578 found = !cmp(s->getComposer(), itsConstraints[6]);
579 if (found && !itsConstraints[7].empty())
580 found = !cmp(s->getPerformer(), itsConstraints[7]);
581 if (found && !itsConstraints[8].empty())
582 found = !cmp(s->getGenre(), itsConstraints[8]);
583 if (found && !itsConstraints[9].empty())
584 found = !cmp(s->getDate(), itsConstraints[9]);
585 if (found && !itsConstraints[10].empty())
586 found = !cmp(s->getComment(), itsConstraints[10]);
589 if (any_found && found)
590 w.addItem(*s);
594 namespace {
596 std::string SEItemToString(const SEItem &ei)
598 std::string result;
599 if (ei.isSong())
601 switch (Config.search_engine_display_mode)
603 case DisplayMode::Classic:
604 result = Format::stringify<char>(Config.song_list_format, &ei.song());
605 break;
606 case DisplayMode::Columns:
607 result = Format::stringify<char>(Config.song_columns_mode_format, &ei.song());
608 break;
611 else
612 result = ei.buffer().str();
613 return result;
616 bool SEItemEntryMatcher(const Regex::Regex &rx, const NC::Menu<SEItem>::Item &item, bool filter)
618 if (item.isSeparator() || !item.value().isSong())
619 return filter;
620 return Regex::search(SEItemToString(item.value()), rx, Config.ignore_diacritics);