diff --git a/src/actions.cpp b/src/actions.cpp index 4802593f..a4cacb4c 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -337,9 +337,8 @@ void Action::FindItem(const FindDirection fd) { using Global::wFooter; - List *mList = myScreen->GetList(); - if (!mList) - return; + Searchable *w = dynamic_cast(myScreen); + assert(w); LockStatusbar(); Statusbar() << "Find " << (fd == fdForward ? "forward" : "backward") << ": "; @@ -349,7 +348,7 @@ void Action::FindItem(const FindDirection fd) if (!findme.empty()) ShowMessage("Searching..."); - bool success = mList->Search(findme, 0, REG_ICASE | Config.regex_type); + bool success = w->search(findme); if (findme.empty()) return; @@ -359,10 +358,10 @@ void Action::FindItem(const FindDirection fd) else ShowMessage("Unable to find \"%s\"", findme.c_str()); - if (fd == fdForward) - mList->NextFound(Config.wrapped_search); - else - mList->PrevFound(Config.wrapped_search); + if (fd == fdForward) + w->nextFound(Config.wrapped_search); + else + w->prevFound(Config.wrapped_search); if (myScreen == myPlaylist) myPlaylist->EnableHighlighting(); @@ -377,7 +376,7 @@ void Action::ListsChangeFinisher() # endif // HAVE_TAGLIB_H ) { - if (myScreen->ActiveWindow() == myLibrary->Artists) + if (myScreen->ActiveWindow() == myLibrary->Tags) { myLibrary->Albums->Clear(); myLibrary->Songs->Clear(); @@ -816,7 +815,7 @@ void Delete::Run() ShowMessage("Deleting directories is disabled by default, see man page for more details"); return; } - if (myBrowser->isParentDir(myBrowser->Main()->Choice())) + if (myBrowser->isParentDirectory(item)) return; std::string name = item.type == itSong ? item.song->getName() : item.name; @@ -1038,9 +1037,6 @@ void MoveSelectedItemsTo::Run() ShowMessage("No selected items to move"); return; } - // remove search results as we may move them to different positions, but - // search rememebers positions and may point to wrong ones after that. - myPlaylist->Items->Search(""); size_t pos = myPlaylist->Items->Choice(); std::vector list; myPlaylist->Items->GetSelected(list); @@ -1163,13 +1159,11 @@ void ToggleDisplayMode::Run() { myPlaylist->Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylist)); myPlaylist->Items->SetTitle(Config.titles_visibility ? Display::Columns(myPlaylist->Items->GetWidth()) : ""); - myPlaylist->Items->SetItemStringifier(Playlist::SongInColumnsToString); } else { myPlaylist->Items->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylist, Config.song_list_format)); myPlaylist->Items->SetTitle(""); - myPlaylist->Items->SetItemStringifier(Playlist::SongToString); } } else if (myScreen == myBrowser) @@ -1192,12 +1186,10 @@ void ToggleDisplayMode::Run() if (Config.columns_in_playlist_editor) { myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylistEditor)); - myPlaylistEditor->Content->SetItemStringifier(Playlist::SongInColumnsToString); } else { myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylistEditor, Config.song_list_format)); - myPlaylistEditor->Content->SetItemStringifier(Playlist::SongToString); } } } @@ -1406,8 +1398,8 @@ void EditSong::Run() bool EditLibraryTag::canBeRun() const { # ifdef HAVE_TAGLIB_H - return myScreen->ActiveWindow() == myLibrary->Artists - && !myLibrary->Artists->Empty(); + return myScreen->ActiveWindow() == myLibrary->Tags + && !myLibrary->Tags->Empty(); # else return false; # endif // HAVE_TAGLIB_H @@ -1422,13 +1414,13 @@ void EditLibraryTag::Run() return; LockStatusbar(); Statusbar() << fmtBold << tagTypeToString(Config.media_lib_primary_tag) << fmtBoldEnd << ": "; - std::string new_tag = wFooter->GetString(myLibrary->Artists->Current().value()); + std::string new_tag = wFooter->GetString(myLibrary->Tags->Current().value()); UnlockStatusbar(); - if (!new_tag.empty() && new_tag != myLibrary->Artists->Current().value()) + if (!new_tag.empty() && new_tag != myLibrary->Tags->Current().value()) { ShowMessage("Updating tags..."); Mpd.StartSearch(1); - Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Artists->Current().value())); + Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Tags->Current().value())); MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag); assert(set); bool success = true; @@ -2064,33 +2056,51 @@ void Find::Run() s->Main()->Flush(); } +bool FindItemBackward::canBeRun() const +{ + return dynamic_cast(myScreen); +} + void FindItemForward::Run() { FindItem(fdForward); ListsChangeFinisher(); } +bool FindItemForward::canBeRun() const +{ + return dynamic_cast(myScreen); +} + void FindItemBackward::Run() { FindItem(fdBackward); ListsChangeFinisher(); } +bool NextFoundItem::canBeRun() const +{ + return dynamic_cast(myScreen); +} + void NextFoundItem::Run() { - List *mList = myScreen->GetList(); - if (!mList) - return; - mList->NextFound(Config.wrapped_search); + Searchable *w = dynamic_cast(myScreen); + assert(w); + w->nextFound(Config.wrapped_search); ListsChangeFinisher(); } +bool PreviousFoundItem::canBeRun() const +{ + return dynamic_cast(myScreen); +} + void PreviousFoundItem::Run() { - List *mList = myScreen->GetList(); - if (!mList) - return; - mList->PrevFound(Config.wrapped_search); + Searchable *w = dynamic_cast(myScreen); + assert(w); + w->prevFound(Config.wrapped_search); ListsChangeFinisher(); } @@ -2209,7 +2219,7 @@ void ToggleBrowserSortMode::Run() bool ToggleLibraryTagType::canBeRun() const { - return (myScreen->ActiveWindow() == myLibrary->Artists) + return (myScreen->ActiveWindow() == myLibrary->Tags) || (myLibrary->Columns() == 2 && myScreen->ActiveWindow() == myLibrary->Albums); } @@ -2233,8 +2243,8 @@ void ToggleLibraryTagType::Run() { Config.media_lib_primary_tag = new_tagitem; std::string item_type = tagTypeToString(Config.media_lib_primary_tag); - myLibrary->Artists->SetTitle(Config.titles_visibility ? item_type + "s" : ""); - myLibrary->Artists->Reset(); + myLibrary->Tags->SetTitle(Config.titles_visibility ? item_type + "s" : ""); + myLibrary->Tags->Reset(); lowercase(item_type); if (myLibrary->Columns() == 2) { @@ -2246,8 +2256,8 @@ void ToggleLibraryTagType::Run() } else { - myLibrary->Artists->Clear(); - myLibrary->Artists->Display(); + myLibrary->Tags->Clear(); + myLibrary->Tags->Display(); } ShowMessage("Switched to list of %s tag", item_type.c_str()); } @@ -2345,8 +2355,8 @@ void ShowArtistInfo::Run() if (s) artist = s->getArtist(); - else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Artists && !myLibrary->Artists->Empty()) - artist = myLibrary->Artists->Current().value(); + else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Tags && !myLibrary->Tags->Empty()) + artist = myLibrary->Tags->Current().value(); if (!artist.empty() && myLastfm->SetArtistInfoArgs(artist, Config.lastfm_preferred_language)) myLastfm->SwitchTo(); diff --git a/src/actions.h b/src/actions.h index fc4141c6..bc920c7a 100644 --- a/src/actions.h +++ b/src/actions.h @@ -655,24 +655,28 @@ struct Find : public Action struct FindItemForward : public Action { FindItemForward() : Action(aFindItemForward, "find_item_forward") { } + virtual bool canBeRun() const; virtual void Run(); }; struct FindItemBackward : public Action { FindItemBackward() : Action(aFindItemBackward, "find_item_backward") { } + virtual bool canBeRun() const; virtual void Run(); }; struct NextFoundItem : public Action { NextFoundItem() : Action(aNextFoundItem, "next_found_item") { } + virtual bool canBeRun() const; virtual void Run(); }; struct PreviousFoundItem : public Action { PreviousFoundItem() : Action(aPreviousFoundItem, "previous_found_item") { } + virtual bool canBeRun() const; virtual void Run(); }; diff --git a/src/browser.cpp b/src/browser.cpp index fed5c0bd..82cba855 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -32,10 +32,8 @@ #include "playlist.h" #include "settings.h" #include "status.h" +#include "tag_editor.h" #include "utility/comparators.h" -#ifdef HAVE_TAGLIB_H -# include "tag_editor.h" -#endif // HAVE_TAGLIB_H using Global::MainHeight; using Global::MainStartY; @@ -48,7 +46,15 @@ using MPD::itPlaylist; Browser *myBrowser = new Browser; -std::set Browser::SupportedExtensions; +namespace {// + +std::set SupportedExtensions; +bool hasSupportedExtension(const std::string &file); + +std::string ItemToString(const MPD::Item &item); +bool BrowserEntryMatcher(const Regex &rx, const MPD::Item &item, bool filter); + +} void Browser::Init() { @@ -59,7 +65,6 @@ void Browser::Init() w->SetSelectPrefix(Config.selected_item_prefix); w->SetSelectSuffix(Config.selected_item_suffix); w->setItemDisplayer(Display::Items); - w->SetItemStringifier(ItemToString); if (SupportedExtensions.empty()) Mpd.GetSupportedExtensions(SupportedExtensions); @@ -126,7 +131,7 @@ void Browser::EnterPressed() { case itDirectory: { - if (isParentDir(w->Choice())) + if (isParentDirectory(item)) { size_t slash = itsBrowsedDir.rfind("/"); if (slash != std::string::npos) @@ -171,10 +176,11 @@ void Browser::SpacePressed() return; } - if (isParentDir(w->Choice())) + const MPD::Item &item = w->Current().value(); + + if (isParentDirectory(item)) return; - const MPD::Item &item = w->Current().value(); switch (item.type) { case itDirectory: @@ -319,6 +325,8 @@ void Browser::GetSelectedSongs(MPD::SongList &v) } } +/***********************************************************************/ + std::string Browser::currentFilter() { return RegexFilter::currentFilter(*w); @@ -327,26 +335,32 @@ std::string Browser::currentFilter() void Browser::applyFilter(const std::string &filter) { w->ShowAll(); - auto fun = [](const Regex &rx, Menu &menu, const Menu::Item &item) { - if (item.value().type == MPD::itDirectory && item.value().name == "..") - return true; - return rx.match(menu.Stringify(item)); - }; + auto fun = std::bind(BrowserEntryMatcher, _1, _2, true); auto rx = RegexFilter(filter, Config.regex_type, fun); - w->Filter(w->Begin(), w->End(), rx); + w->filter(w->Begin(), w->End(), rx); } -bool Browser::hasSupportedExtension(const std::string &file) +/***********************************************************************/ + +bool Browser::search(const std::string &constraint) { - size_t last_dot = file.rfind("."); - if (last_dot > file.length()) - return false; - - std::string ext = file.substr(last_dot+1); - lowercase(ext); - return SupportedExtensions.find(ext) != SupportedExtensions.end(); + auto fun = std::bind(BrowserEntryMatcher, _1, _2, false); + auto rx = RegexFilter(constraint, Config.regex_type, fun); + return w->search(w->Begin(), w->End(), rx); } +void Browser::nextFound(bool wrap) +{ + w->NextFound(wrap); +} + +void Browser::prevFound(bool wrap) +{ + w->PrevFound(wrap); +} + +/***********************************************************************/ + void Browser::LocateSong(const MPD::Song &s) { if (s.getDirectory().empty()) @@ -592,7 +606,20 @@ void Browser::UpdateItemList() w->Refresh(); } -std::string Browser::ItemToString(const MPD::Item &item) +namespace {// + +bool hasSupportedExtension(const std::string &file) +{ + size_t last_dot = file.rfind("."); + if (last_dot > file.length()) + return false; + + std::string ext = file.substr(last_dot+1); + lowercase(ext); + return SupportedExtensions.find(ext) != SupportedExtensions.end(); +} + +std::string ItemToString(const MPD::Item &item) { std::string result; switch (item.type) @@ -601,13 +628,23 @@ std::string Browser::ItemToString(const MPD::Item &item) result = "[" + getBasename(item.name) + "]"; break; case MPD::itSong: - if (!Config.columns_in_browser) - result = item.song->toString(Config.song_list_format_dollar_free); + if (Config.columns_in_browser) + result = item.song->toString(Config.song_in_columns_to_string_format); else - result = Playlist::SongInColumnsToString(*item.song); + result = item.song->toString(Config.song_list_format_dollar_free); + break; case MPD::itPlaylist: result = Config.browser_playlist_prefix.Str() + getBasename(item.name); + break; } return result; } +bool BrowserEntryMatcher(const Regex &rx, const MPD::Item &item, bool filter) +{ + if (Browser::isParentDirectory(item)) + return filter; + return rx.match(ItemToString(item)); +} + +} diff --git a/src/browser.h b/src/browser.h index 2e005380..1663d39f 100644 --- a/src/browser.h +++ b/src/browser.h @@ -26,7 +26,7 @@ #include "regex_filter.h" #include "screen.h" -class Browser : public Screen< Menu >, public Filterable +class Browser : public Screen< Menu >, public Filterable, public Searchable { public: Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/") { } @@ -48,9 +48,15 @@ class Browser : public Screen< Menu >, public Filterable virtual void ReverseSelection(); virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual List *GetList() { return w; } virtual bool isMergable() { return true; } @@ -68,18 +74,15 @@ class Browser : public Screen< Menu >, public Filterable # endif // !WIN32 void UpdateItemList(); - bool isParentDir(size_t pos) { return itsBrowsedDir != "/" && pos == 0; } + static bool isParentDirectory(const MPD::Item &item) { + return item.type == MPD::itDirectory && item.name == ".."; + } protected: virtual void Init(); virtual bool isLockable() { return true; } private: - static bool hasSupportedExtension(const std::string &); - static std::string ItemToString(const MPD::Item &item); - - static std::set SupportedExtensions; - bool itsBrowseLocally; size_t itsScrollBeginning; std::string itsBrowsedDir; diff --git a/src/interfaces.h b/src/interfaces.h index d1c0a825..e99e6591 100644 --- a/src/interfaces.h +++ b/src/interfaces.h @@ -30,4 +30,11 @@ struct Filterable virtual void applyFilter(const std::string &filter) = 0; }; +struct Searchable +{ + virtual bool search(const std::string &constraint) = 0; + virtual void nextFound(bool wrap) = 0; + virtual void prevFound(bool wrap) = 0; +}; + #endif // _INTERFACES_H diff --git a/src/media_library.cpp b/src/media_library.cpp index 544958a2..bffb10a0 100644 --- a/src/media_library.cpp +++ b/src/media_library.cpp @@ -28,6 +28,7 @@ #include "media_library.h" #include "mpdpp.h" #include "playlist.h" +#include "regex_filter.h" #include "status.h" #include "utility/comparators.h" #include "utility/type_conversions.h" @@ -38,19 +39,39 @@ using Global::myScreen; MediaLibrary *myLibrary = new MediaLibrary; -bool MediaLibrary::hasTwoColumns; -size_t MediaLibrary::itsLeftColStartX; -size_t MediaLibrary::itsLeftColWidth; -size_t MediaLibrary::itsMiddleColWidth; -size_t MediaLibrary::itsMiddleColStartX; -size_t MediaLibrary::itsRightColWidth; -size_t MediaLibrary::itsRightColStartX; +namespace {// + +bool hasTwoColumns; +size_t itsLeftColStartX; +size_t itsLeftColWidth; +size_t itsMiddleColWidth; +size_t itsMiddleColStartX; +size_t itsRightColWidth; +size_t itsRightColStartX; // this string marks the position in middle column that works as "All tracks" option. it's // assigned to Date in SearchConstraint class since date normally cannot contain other chars // than ciphers and -'s (0x7f is interpreted as backspace keycode, so it's quite safe to assume // that it won't appear in any tag, let alone date). -const char MediaLibrary::AllTracksMarker[] = "\x7f"; +const std::string AllTracksMarker = "\x7f_\x7f_\x7f"; + +typedef MediaLibrary::SearchConstraints SearchConstraints; + +std::string AlbumToString(const SearchConstraints &sc); +std::string SongToString(const MPD::Song &s); + +bool TagEntryMatcher(const Regex &rx, const std::string &tag); +bool AlbumEntryMatcher(const Regex &rx, const Menu::Item &item, bool filter); +bool SongEntryMatcher(const Regex &rx, const MPD::Song &s); + +void DisplayAlbums(Menu &menu); +void DisplayPrimaryTags(Menu &menu); + +bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b); +bool SortAllTracks(const MPD::Song &a, const MPD::Song &b); +bool SortSearchConstraints(const SearchConstraints &a, const SearchConstraints &b); + +} void MediaLibrary::Init() { @@ -61,13 +82,13 @@ void MediaLibrary::Init() itsRightColWidth = COLS-COLS/3*2-1; itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2; - Artists = new Menu(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, brNone); - Artists->HighlightColor(Config.active_column_color); - Artists->CyclicScrolling(Config.use_cyclic_scrolling); - Artists->CenteredCursor(Config.centered_cursor); - Artists->SetSelectPrefix(Config.selected_item_prefix); - Artists->SetSelectSuffix(Config.selected_item_suffix); - Artists->setItemDisplayer(DisplayPrimaryTags); + Tags = new Menu(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, brNone); + Tags->HighlightColor(Config.active_column_color); + Tags->CyclicScrolling(Config.use_cyclic_scrolling); + Tags->CenteredCursor(Config.centered_cursor); + Tags->SetSelectPrefix(Config.selected_item_prefix); + Tags->SetSelectSuffix(Config.selected_item_suffix); + Tags->setItemDisplayer(DisplayPrimaryTags); Albums = new Menu(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, brNone); Albums->HighlightColor(Config.main_highlight_color); @@ -76,7 +97,6 @@ void MediaLibrary::Init() Albums->SetSelectPrefix(Config.selected_item_prefix); Albums->SetSelectSuffix(Config.selected_item_suffix); Albums->setItemDisplayer(DisplayAlbums); - Albums->SetItemStringifier(AlbumToString); Songs = new Menu(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, Config.titles_visibility ? "Songs" : "", Config.main_color, brNone); Songs->HighlightColor(Config.main_highlight_color); @@ -85,9 +105,8 @@ void MediaLibrary::Init() Songs->SetSelectPrefix(Config.selected_item_prefix); Songs->SetSelectSuffix(Config.selected_item_suffix); Songs->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_library_format)); - Songs->SetItemStringifier(SongToString); - w = Artists; + w = Tags; isInitialized = 1; } @@ -112,11 +131,11 @@ void MediaLibrary::Resize() itsRightColWidth = width-itsMiddleColWidth-1; } - Artists->Resize(itsLeftColWidth, MainHeight); + Tags->Resize(itsLeftColWidth, MainHeight); Albums->Resize(itsMiddleColWidth, MainHeight); Songs->Resize(itsRightColWidth, MainHeight); - Artists->MoveTo(itsLeftColStartX, MainStartY); + Tags->MoveTo(itsLeftColStartX, MainStartY); Albums->MoveTo(itsMiddleColStartX, MainStartY); Songs->MoveTo(itsRightColStartX, MainStartY); @@ -125,7 +144,7 @@ void MediaLibrary::Resize() void MediaLibrary::Refresh() { - Artists->Display(); + Tags->Display(); mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight); Albums->Display(); mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight); @@ -149,13 +168,13 @@ void MediaLibrary::SwitchTo() { hasTwoColumns = !hasTwoColumns; hasToBeResized = 1; - Artists->Clear(); + Tags->Clear(); Albums->Clear(); Albums->Reset(); Songs->Clear(); if (hasTwoColumns) { - if (w == Artists) + if (w == Tags) NextColumn(); if (Config.titles_visibility) { @@ -195,7 +214,7 @@ std::basic_string MediaLibrary::Title() void MediaLibrary::Update() { - if (!hasTwoColumns && Artists->ReallyEmpty()) + if (!hasTwoColumns && Tags->ReallyEmpty()) { MPD::TagList list; Albums->Clear(); @@ -207,13 +226,13 @@ void MediaLibrary::Update() if (it->empty() && !Config.media_library_display_empty_tag) continue; utf_to_locale(*it); - Artists->AddItem(*it); + Tags->AddItem(*it); } - Artists->Window::Clear(); - Artists->Refresh(); + Tags->Window::Clear(); + Tags->Refresh(); } - if (!hasTwoColumns && !Artists->Empty() && Albums->ReallyEmpty() && Songs->ReallyEmpty()) + if (!hasTwoColumns && !Tags->Empty() && Albums->ReallyEmpty() && Songs->ReallyEmpty()) { // idle has to be blocked for now since it would be enabled and // disabled a few times by each mpd command, which makes no sense @@ -221,9 +240,9 @@ void MediaLibrary::Update() Mpd.BlockIdle(1); Albums->Reset(); MPD::TagList list; - locale_to_utf(Artists->Current().value()); + locale_to_utf(Tags->Current().value()); Mpd.StartFieldSearch(MPD_TAG_ALBUM); - Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current().value()); + Mpd.AddSearch(Config.media_lib_primary_tag, Tags->Current().value()); Mpd.CommitSearchTags([&list](std::string &&album) { list.push_back(album); }); @@ -232,7 +251,7 @@ void MediaLibrary::Update() if (Config.media_library_display_date) { Mpd.StartFieldSearch(MPD_TAG_DATE); - Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current().value()); + Mpd.AddSearch(Config.media_lib_primary_tag, Tags->Current().value()); Mpd.AddSearch(MPD_TAG_ALBUM, *album); utf_to_locale(*album); Mpd.CommitSearchTags([this, &album](std::string &&date) { @@ -246,9 +265,9 @@ void MediaLibrary::Update() Albums->AddItem(SearchConstraints(*album, "")); } } - utf_to_locale(Artists->Current().value()); + utf_to_locale(Tags->Current().value()); if (!Albums->Empty()) - std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting()); + std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints); if (Albums->Size() > 1) { Albums->AddSeparator(); @@ -306,23 +325,23 @@ void MediaLibrary::Update() } Mpd.BlockIdle(0); if (!Albums->Empty()) - std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting()); + std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints); Albums->Refresh(); } - if (!hasTwoColumns && !Artists->Empty() && w == Albums && Albums->ReallyEmpty()) + if (!hasTwoColumns && !Tags->Empty() && w == Albums && Albums->ReallyEmpty()) { Albums->HighlightColor(Config.main_highlight_color); - Artists->HighlightColor(Config.active_column_color); - w = Artists; + Tags->HighlightColor(Config.active_column_color); + w = Tags; } - if (!(hasTwoColumns ? Albums->Empty() : Artists->Empty()) && Songs->ReallyEmpty()) + if (!(hasTwoColumns ? Albums->Empty() : Tags->Empty()) && Songs->ReallyEmpty()) { Songs->Reset(); Mpd.StartSearch(1); - Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().value().PrimaryTag : Artists->Current().value())); + Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().value().PrimaryTag : Tags->Current().value())); if (Albums->Current().value().Date != AllTracksMarker) { Mpd.AddSearch(MPD_TAG_ALBUM, locale_to_utf_cpy(Albums->Current().value().Album)); @@ -347,10 +366,10 @@ void MediaLibrary::SpacePressed() { if (Config.space_selects) { - if (w == Artists) + if (w == Tags) { - size_t i = Artists->Choice(); - Artists->at(i).setSelected(!Artists->at(i).isSelected()); + size_t i = Tags->Choice(); + Tags->at(i).setSelected(!Tags->at(i).isSelected()); Albums->Clear(); Songs->Clear(); } @@ -376,22 +395,22 @@ void MediaLibrary::SpacePressed() void MediaLibrary::MouseButtonPressed(MEVENT me) { - if (!Artists->Empty() && Artists->hasCoords(me.x, me.y)) + if (!Tags->Empty() && Tags->hasCoords(me.x, me.y)) { - if (w != Artists) + if (w != Tags) { PrevColumn(); PrevColumn(); } - if (size_t(me.y) < Artists->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))) + if (size_t(me.y) < Tags->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))) { - Artists->Goto(me.y); + Tags->Goto(me.y); if (me.bstate & BUTTON3_PRESSED) { - size_t pos = Artists->Choice(); + size_t pos = Tags->Choice(); SpacePressed(); - if (pos < Artists->Size()-1) - Artists->Scroll(wUp); + if (pos < Tags->Size()-1) + Tags->Scroll(wUp); } } else @@ -402,7 +421,7 @@ void MediaLibrary::MouseButtonPressed(MEVENT me) else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y)) { if (w != Albums) - w == Artists ? NextColumn() : PrevColumn(); + w == Tags ? NextColumn() : PrevColumn(); if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))) { Albums->Goto(me.y); @@ -450,8 +469,8 @@ MPD::Song *MediaLibrary::CurrentSong() List *MediaLibrary::GetList() { - if (w == Artists) - return Artists; + if (w == Tags) + return Tags; else if (w == Albums) return Albums; else if (w == Songs) @@ -462,8 +481,8 @@ List *MediaLibrary::GetList() void MediaLibrary::ReverseSelection() { - if (w == Artists) - Artists->ReverseSelection(); + if (w == Tags) + Tags->ReverseSelection(); else if (w == Albums) Albums->ReverseSelection(); else if (w == Songs) @@ -473,16 +492,16 @@ void MediaLibrary::ReverseSelection() void MediaLibrary::GetSelectedSongs(MPD::SongList &v) { std::vector selected; - if (w == Artists && !Artists->Empty()) + if (w == Tags && !Tags->Empty()) { - Artists->GetSelected(selected); + Tags->GetSelected(selected); if (selected.empty()) - selected.push_back(Artists->Choice()); + selected.push_back(Tags->Choice()); for (auto it = selected.begin(); it != selected.end(); ++it) { MPD::SongList list; Mpd.StartSearch(1); - Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Artists->at(*it).value())); + Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Tags->at(*it).value())); Mpd.CommitSearchSongs([&list](MPD::Song &&s) { list.push_back(s); }); @@ -508,7 +527,7 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v) Mpd.StartSearch(1); Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns ? Albums->at(*it).value().PrimaryTag - : locale_to_utf_cpy(Artists->Current().value())); + : locale_to_utf_cpy(Tags->Current().value())); Mpd.AddSearch(MPD_TAG_ALBUM, Albums->at(*it).value().Album); Mpd.AddSearch(MPD_TAG_DATE, Albums->at(*it).value().Date); Mpd.CommitSearchSongs([&v](MPD::Song &&s) { @@ -527,13 +546,15 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v) } } +/***********************************************************************/ + std::string MediaLibrary::currentFilter() { std::string filter; - if (w == Artists) - filter = RegexFilter::currentFilter(*Artists); + if (w == Tags) + filter = RegexFilter::currentFilter(*Tags); else if (w == Albums) - filter = RegexFilter::currentFilter(*Albums); + filter = RegexItemFilter::currentFilter(*Albums); else if (w == Songs) filter = RegexFilter::currentFilter(*Songs); return filter; @@ -541,35 +562,85 @@ std::string MediaLibrary::currentFilter() void MediaLibrary::applyFilter(const std::string &filter) { - if (w == Artists) + if (w == Tags) { - Artists->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Artists->Filter(Artists->Begin(), Artists->End(), rx); + Tags->ShowAll(); + auto rx = RegexFilter(filter, Config.regex_type, TagEntryMatcher); + Tags->filter(Tags->Begin(), Tags->End(), rx); } else if (w == Albums) { Albums->ShowAll(); - auto fun = [](const Regex &rx, Menu &menu, const Menu::Item &item) { - if (item.isSeparator() || item.value().Date == AllTracksMarker) - return true; - return rx.match(menu.Stringify(item)); - }; - auto rx = RegexFilter(filter, Config.regex_type, fun); - Albums->Filter(Albums->Begin(), Albums->End(), rx); + auto fun = std::bind(AlbumEntryMatcher, _1, _2, true); + auto rx = RegexItemFilter(filter, Config.regex_type, fun); + Albums->filter(Albums->Begin(), Albums->End(), rx); } else if (w == Songs) { Songs->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Songs->Filter(Songs->Begin(), Songs->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, SongEntryMatcher); + Songs->filter(Songs->Begin(), Songs->End(), rx); } } +/***********************************************************************/ + +bool MediaLibrary::search(const std::string &constraint) +{ + bool result = false; + if (w == Tags) + { + auto rx = RegexFilter(constraint, Config.regex_type, TagEntryMatcher); + result = Tags->search(Tags->Begin(), Tags->End(), rx); + } + else if (w == Albums) + { + auto fun = std::bind(AlbumEntryMatcher, _1, _2, false); + auto rx = RegexItemFilter(constraint, Config.regex_type, fun); + result = Albums->search(Albums->Begin(), Albums->End(), rx); + } + else if (w == Songs) + { + auto rx = RegexFilter(constraint, Config.regex_type, SongEntryMatcher); + result = Songs->search(Songs->Begin(), Songs->End(), rx); + } + return result; +} + +void MediaLibrary::nextFound(bool wrap) +{ + if (w == Tags) + Tags->NextFound(wrap); + else if (w == Albums) + Albums->NextFound(wrap); + else if (w == Songs) + Songs->NextFound(wrap); +} + +void MediaLibrary::prevFound(bool wrap) +{ + if (w == Tags) + Tags->PrevFound(wrap); + else if (w == Albums) + Albums->PrevFound(wrap); + else if (w == Songs) + Songs->PrevFound(wrap); +} + +/***********************************************************************/ + +int MediaLibrary::Columns() +{ + if (hasTwoColumns) + return 2; + else + return 3; +} + bool MediaLibrary::isNextColumnAvailable() { - assert(!hasTwoColumns || w != Artists); - if (w == Artists) + assert(!hasTwoColumns || w != Tags); + if (w == Tags) { if (!Albums->ReallyEmpty() && !Songs->ReallyEmpty()) return true; @@ -584,9 +655,9 @@ bool MediaLibrary::isNextColumnAvailable() void MediaLibrary::NextColumn() { - if (w == Artists) + if (w == Tags) { - Artists->HighlightColor(Config.main_highlight_color); + Tags->HighlightColor(Config.main_highlight_color); w->Refresh(); w = Albums; Albums->HighlightColor(Config.active_column_color); @@ -602,15 +673,15 @@ void MediaLibrary::NextColumn() bool MediaLibrary::isPrevColumnAvailable() { - assert(!hasTwoColumns || w != Artists); + assert(!hasTwoColumns || w != Tags); if (w == Songs) { - if (!Albums->ReallyEmpty() && (hasTwoColumns || !Artists->ReallyEmpty())) + if (!Albums->ReallyEmpty() && (hasTwoColumns || !Tags->ReallyEmpty())) return true; } else if (w == Albums) { - if (!hasTwoColumns && !Artists->ReallyEmpty()) + if (!hasTwoColumns && !Tags->ReallyEmpty()) return true; } return false; @@ -629,21 +700,13 @@ void MediaLibrary::PrevColumn() { Albums->HighlightColor(Config.main_highlight_color); w->Refresh(); - w = Artists; - Artists->HighlightColor(Config.active_column_color); + w = Tags; + Tags->HighlightColor(Config.active_column_color); } } void MediaLibrary::LocateSong(const MPD::Song &s) { - if (Mpd.Version() < 14) - { - // = 0.14.0"); - return; - } std::string primary_tag; switch (Config.media_lib_primary_tag) { @@ -681,16 +744,16 @@ void MediaLibrary::LocateSong(const MPD::Song &s) if (!hasTwoColumns) { - Artists->ShowAll(); - if (Artists->Empty()) + Tags->ShowAll(); + if (Tags->Empty()) Update(); - if (primary_tag != Artists->Current().value()) + if (primary_tag != Tags->Current().value()) { - for (size_t i = 0; i < Artists->Size(); ++i) + for (size_t i = 0; i < Tags->Size(); ++i) { - if (primary_tag == (*Artists)[i].value()) + if (primary_tag == (*Tags)[i].value()) { - Artists->Highlight(i); + Tags->Highlight(i); Albums->Clear(); Songs->Clear(); break; @@ -738,7 +801,7 @@ void MediaLibrary::LocateSong(const MPD::Song &s) } } - Artists->HighlightColor(Config.main_highlight_color); + Tags->HighlightColor(Config.main_highlight_color); Albums->HighlightColor(Config.main_highlight_color); Songs->HighlightColor(Config.active_column_color); w = Songs; @@ -759,12 +822,12 @@ void MediaLibrary::AddToPlaylist(bool add_n_play) if (myPlaylist->Add(list, add_n_play)) { - if ((!Artists->Empty() && w == Artists) + if ((!Tags->Empty() && w == Tags) || (w == Albums && Albums->Current().value().Date == AllTracksMarker)) { std::string tag_type = tagTypeToString(Config.media_lib_primary_tag); lowercase(tag_type); - ShowMessage("Songs with %s = \"%s\" added", tag_type.c_str(), Artists->Current().value().c_str()); + ShowMessage("Songs with %s = \"%s\" added", tag_type.c_str(), Tags->Current().value().c_str()); } else if (w == Albums) ShowMessage("Songs from album \"%s\" added", Albums->Current().value().Album.c_str()); @@ -774,7 +837,7 @@ void MediaLibrary::AddToPlaylist(bool add_n_play) if (!add_n_play) { w->Scroll(wDown); - if (w == Artists) + if (w == Tags) { Albums->Clear(); Songs->Clear(); @@ -784,30 +847,62 @@ void MediaLibrary::AddToPlaylist(bool add_n_play) } } -std::string MediaLibrary::SongToString(const MPD::Song &s) +namespace {// + +std::string AlbumToString(const SearchConstraints &sc) +{ + std::string result; + if (sc.Date == AllTracksMarker) + result = "All tracks"; + else + { + if (hasTwoColumns) + { + if (sc.PrimaryTag.empty()) + result += Config.empty_tag; + else + result += sc.PrimaryTag; + result += " - "; + } + if (Config.media_lib_primary_tag != MPD_TAG_DATE && !sc.Date.empty()) + result += "(" + sc.Date + ") "; + result += sc.Album.empty() ? "" : sc.Album; + } + return result; +} + +std::string SongToString(const MPD::Song &s) { return s.toString(Config.song_library_format); } -std::string MediaLibrary::AlbumToString(const SearchConstraints &sc) +/***********************************************************************/ + +bool TagEntryMatcher(const Regex &rx, const std::string &tag) { - if (sc.Date == AllTracksMarker) - return "All tracks"; - std::string result; - if (myLibrary->hasTwoColumns) - (result += sc.PrimaryTag.empty() ? Config.empty_tag : sc.PrimaryTag) += " - "; - if ((!myLibrary || Config.media_lib_primary_tag != MPD_TAG_DATE) && !sc.Date.empty()) - ((result += "(") += sc.Date) += ") "; - result += sc.Album.empty() ? "" : sc.Album; - return result; + return rx.match(tag); } -void MediaLibrary::DisplayAlbums(Menu &menu) +bool AlbumEntryMatcher(const Regex &rx, const Menu::Item &item, bool filter) +{ + if (item.isSeparator() || item.value().Date == AllTracksMarker) + return filter; + return rx.match(AlbumToString(item.value())); +} + +bool SongEntryMatcher(const Regex &rx, const MPD::Song &s) +{ + return rx.match(SongToString(s)); +} + +/***********************************************************************/ + +void DisplayAlbums(Menu &menu) { menu << AlbumToString(menu.Drawn().value()); } -void MediaLibrary::DisplayPrimaryTags(Menu &menu) +void DisplayPrimaryTags(Menu &menu) { const std::string &tag = menu.Drawn().value(); if (tag.empty()) @@ -816,18 +911,9 @@ void MediaLibrary::DisplayPrimaryTags(Menu &menu) menu << tag; } -bool MediaLibrary::SearchConstraintsSorting::operator()(const SearchConstraints &a, const SearchConstraints &b) const -{ - int result; - CaseInsensitiveStringComparison cmp; - result = cmp(a.PrimaryTag, b.PrimaryTag); - if (result != 0) - return result < 0; - result = cmp(a.Date, b.Date); - return (result == 0 ? cmp(a.Album, b.Album) : result) < 0; -} +/***********************************************************************/ -bool MediaLibrary::SortSongsByTrack(const MPD::Song &a, const MPD::Song &b) +bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b) { if (a.getDisc() == b.getDisc()) return stringToInt(a.getTrack()) < stringToInt(b.getTrack()); @@ -835,13 +921,27 @@ bool MediaLibrary::SortSongsByTrack(const MPD::Song &a, const MPD::Song &b) return stringToInt(a.getDisc()) < stringToInt(b.getDisc()); } -bool MediaLibrary::SortAllTracks(const MPD::Song &a, const MPD::Song &b) +bool SortAllTracks(const MPD::Song &a, const MPD::Song &b) { static MPD::Song::GetFunction gets[] = { &MPD::Song::getDate, &MPD::Song::getAlbum, &MPD::Song::getDisc, 0 }; CaseInsensitiveStringComparison cmp; for (MPD::Song::GetFunction *get = gets; *get; ++get) if (int ret = cmp(a.getTags(*get), b.getTags(*get))) return ret < 0; - return a.getTrack() < b.getTrack(); + return a.getTrack() < b.getTrack(); } +bool SortSearchConstraints(const SearchConstraints &a, const SearchConstraints &b) +{ + int result; + CaseInsensitiveStringComparison cmp; + result = cmp(a.PrimaryTag, b.PrimaryTag); + if (result != 0) + return result < 0; + result = cmp(a.Date, b.Date); + if (result != 0) + return result < 0; + return cmp(a.Album, b.Album) < 0; +} + +} diff --git a/src/media_library.h b/src/media_library.h index d2836571..48c2e1a0 100644 --- a/src/media_library.h +++ b/src/media_library.h @@ -23,27 +23,10 @@ #include "interfaces.h" #include "ncmpcpp.h" -#include "regex_filter.h" #include "screen.h" -class MediaLibrary : public Screen, public Filterable +class MediaLibrary : public Screen, public Filterable, public Searchable { - struct SearchConstraints - { - SearchConstraints() { } - SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) : PrimaryTag(tag), Album(album), Date(date) { } - SearchConstraints(const std::string &album, const std::string &date) : Album(album), Date(date) { } - - std::string PrimaryTag; - std::string Album; - std::string Date; - }; - - struct SearchConstraintsSorting - { - bool operator()(const SearchConstraints &a, const SearchConstraints &b) const; - }; - public: virtual void SwitchTo(); virtual void Resize(); @@ -65,14 +48,20 @@ class MediaLibrary : public Screen, public Filterable virtual void ReverseSelection(); virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual List *GetList(); virtual bool isMergable() { return true; } - int Columns() { return hasTwoColumns ? 2 : 3; } + int Columns(); bool isNextColumnAvailable(); void NextColumn(); bool isPrevColumnAvailable(); @@ -80,7 +69,20 @@ class MediaLibrary : public Screen, public Filterable void LocateSong(const MPD::Song &); - Menu *Artists; + struct SearchConstraints + { + SearchConstraints() { } + SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) + : PrimaryTag(tag), Album(album), Date(date) { } + SearchConstraints(const std::string &album, const std::string &date) + : Album(album), Date(date) { } + + std::string PrimaryTag; + std::string Album; + std::string Date; + }; + + Menu *Tags; Menu *Albums; Menu *Songs; @@ -90,24 +92,6 @@ class MediaLibrary : public Screen, public Filterable private: void AddToPlaylist(bool); - - static std::string SongToString(const MPD::Song &s); - static std::string AlbumToString(const SearchConstraints &); - static void DisplayAlbums(Menu &menu); - static void DisplayPrimaryTags(Menu &menu); - - static bool SortSongsByTrack(const MPD::Song &, const MPD::Song &); - static bool SortAllTracks(const MPD::Song &, const MPD::Song &); - - static bool hasTwoColumns; - static size_t itsLeftColStartX; - static size_t itsLeftColWidth; - static size_t itsMiddleColWidth; - static size_t itsMiddleColStartX; - static size_t itsRightColWidth; - static size_t itsRightColStartX; - - static const char AllTracksMarker[]; }; extern MediaLibrary *myLibrary; diff --git a/src/menu.cpp b/src/menu.cpp index db917a5d..033ca847 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -17,32 +17,3 @@ * Free Software Foundation, Inc., * * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ - -#include "menu.h" - -namespace NCurses { - -template <> std::string Menu::GetItem(size_t pos) -{ - std::string result; - if (m_options_ptr->at(pos)) - { - if (m_item_stringifier) - result = m_item_stringifier((*m_options_ptr)[pos]->value()); - else - result = (*m_options_ptr)[pos]->value(); - } - return result; -} - -template <> std::string Menu::Stringify(const Menu::Item &item) const -{ - std::string result; - if (m_item_stringifier) - result = m_item_stringifier(item.value()); - else - result = item.value(); - return result; -} - -} diff --git a/src/menu.h b/src/menu.h index 20d6c6e6..1386924b 100644 --- a/src/menu.h +++ b/src/menu.h @@ -34,41 +34,20 @@ namespace NCurses { /// List class is an interface for Menu class -/// struct List { /// @return currently highlighted position - /// virtual size_t Choice() const = 0; /// @see Menu::Empty() - /// virtual bool Empty() const = 0; /// @see Menu::Size() - /// virtual size_t Size() const = 0; - - /// @see Menu::Search() - /// - virtual bool Search(const std::string &constraint, size_t beginning = 0, int flags = 0) = 0; - - /// @see Menu::GetSearchConstraint() - /// - virtual const std::string &GetSearchConstraint() = 0; - - /// @see Menu::NextFound() - /// - virtual void NextFound(bool wrap) = 0; - - /// @see Menu::PrevFound() - /// - virtual void PrevFound(bool wrap) = 0; }; /// This template class is generic menu capable of /// holding any std::vector compatible values. -/// template struct Menu : public Window, public List { struct Item @@ -116,10 +95,12 @@ template struct Menu : public Window, public List BaseIterator m_it; explicit ItemIterator(BaseIterator it) : m_it(it) { } + // base iterator's value_type doesn't change between const and non-const + // version, so we need to strip const off ValueT too for proper template + // version to be instantiated. static const bool referenceValue = !std::is_same< - ValueT, typename std::remove_pointer< - typename BaseIterator::value_type - >::type + typename std::remove_const::type, + typename std::remove_pointer::type >::value; template struct getObject { }; template struct getObject { @@ -197,17 +178,9 @@ template struct Menu : public Window, public List /// Function helper prototype used to display each option on the screen. /// If not set by setItemDisplayer(), menu won't display anything. /// @see setItemDisplayer() - /// typedef std::function &)> ItemDisplayer; - /// Function helper prototype used for converting items to strings. - /// If not set by SetItemStringifier(), searching and filtering - /// won't work (note that Menu doesn't need this) - /// @see SetItemStringifier() - /// - typedef std::function ItemStringifier; - - typedef std::function &, const Item &)> FilterFunction; + typedef std::function FilterFunction; /// Constructs an empty menu with given parameters /// @param startx X position of left upper corner of constructed menu @@ -217,43 +190,31 @@ template struct Menu : public Window, public List /// @param title title of constructed menu /// @param color base color of constructed menu /// @param border border of constructed menu - /// Menu(size_t startx, size_t starty, size_t width, size_t height, const std::string &title, Color color, Border border); /// Destroys the object and frees memory - /// virtual ~Menu(); /// Sets helper function that is responsible for displaying items /// @param ptr function pointer that matches the ItemDisplayer prototype - /// void setItemDisplayer(const ItemDisplayer &f) { m_item_displayer = f; } - /// Sets helper function that is responsible for converting items to strings - /// @param f function pointer that matches the ItemStringifier prototype - /// - void SetItemStringifier(const ItemStringifier &f) { m_item_stringifier = f; } - /// Reserves the size for internal container (this just calls std::vector::reserve()) /// @param size requested size - /// void Reserve(size_t size); /// Resizes the list to given size (adequate to std::vector::resize()) /// @param size requested size - /// void ResizeList(size_t size); /// Adds new option to list /// @param item object that has to be added /// @param is_bold defines the initial state of bold attribute /// @param is_static defines the initial state of static attribute - /// void AddItem(const T &item, bool is_bold = 0, bool is_static = 0); /// Adds separator to list - /// void AddSeparator(); /// Inserts new option to list at given position @@ -261,237 +222,182 @@ template struct Menu : public Window, public List /// @param item object that has to be inserted /// @param is_bold defines the initial state of bold attribute /// @param is_static defines the initial state of static attribute - /// void InsertItem(size_t pos, const T &Item, bool is_bold = 0, bool is_static = 0); /// Inserts separator to list at given position /// @param pos initial position of inserted separator - /// void InsertSeparator(size_t pos); /// Deletes item from given position /// @param pos given position of item to be deleted - /// void DeleteItem(size_t pos); /// Swaps the content of two items /// @param one position of first item /// @param two position of second item - /// void Swap(size_t one, size_t two); /// Moves the highlighted position to the given line of window /// @param y Y position of menu window to be highlighted /// @return true if the position is reachable, false otherwise - /// bool Goto(size_t y); /// Checks whether list contains selected positions /// @return true if it contains them, false otherwise - /// bool hasSelected() const; /// Gets positions of items that are selected /// @param v vector to be filled with selected positions numbers - /// void GetSelected(std::vector &v) const; /// Reverses selection of all items in list /// @param beginning beginning of range that has to be reversed - /// void ReverseSelection(size_t beginning = 0); /// Highlights given position /// @param pos position to be highlighted - /// void Highlight(size_t pos); /// @return currently highlighted position - /// size_t Choice() const; - template void Filter(Iterator first, Iterator last, const FilterFunction &f); + void filter(ConstIterator first, ConstIterator last, const FilterFunction &f); - /// Searches the list for a given contraint. It uses ItemStringifier to convert stored items - /// into strings and then performs pattern matching. Note that this supports regular expressions. - /// @param constraint a search constraint to be used - /// @param beginning beginning of range that has to be searched through - /// @param flags regex flags (REG_EXTENDED, REG_ICASE, REG_NOSUB, REG_NEWLINE) - /// @return true if at least one item matched the given pattern, false otherwise - /// - virtual bool Search(const std::string &constraint, size_t beginning = 0, int flags = 0); + bool search(ConstIterator first, ConstIterator last, const FilterFunction &f); - /// @return const reference to currently used search constraint - /// - virtual const std::string &GetSearchConstraint() { return m_search_constraint; } + /// Clears filter results + void clearFilterResults(); + + /// Clears search results + void clearSearchResults(); /// Moves current position in the list to the next found one /// @param wrap if true, this function will go to the first /// found pos after the last one, otherwise it'll do nothing. - /// virtual void NextFound(bool wrap); /// Moves current position in the list to the previous found one /// @param wrap if true, this function will go to the last /// found pos after the first one, otherwise it'll do nothing. - /// virtual void PrevFound(bool wrap); /// @return const reference to currently used filter function - /// const FilterFunction &getFilter() { return m_filter; } /// @return true if list is currently filtered, false otherwise - /// bool isFiltered() { return m_options_ptr == &m_filtered_options; } /// Turns off filtering - /// void ShowAll() { m_options_ptr = &m_options; } /// Turns on filtering - /// void ShowFiltered() { m_options_ptr = &m_filtered_options; } - /// Converts given position in list to string using ItemStringifier - /// if specified and an empty string otherwise - /// @param pos position to be converted - /// @return item converted to string - /// @see setItemDisplayer() - /// - std::string GetItem(size_t pos); - - std::string Stringify(const Item &item) const; - /// Refreshes the menu window /// @see Window::Refresh() - /// virtual void Refresh(); /// Scrolls by given amount of lines /// @param where indicated where exactly one wants to go /// @see Window::Scroll() - /// virtual void Scroll(Where where); /// Cleares all options, used filters etc. It doesn't reset highlighted position though. /// @see Reset() - /// virtual void Clear(); /// Sets highlighted position to 0 - /// void Reset(); /// Sets prefix, that is put before each selected item to indicate its selection /// Note that the passed variable is not deleted along with menu object. /// @param b pointer to buffer that contains the prefix - /// void SetSelectPrefix(const Buffer &b) { m_selected_prefix = b; } /// Sets suffix, that is put after each selected item to indicate its selection /// Note that the passed variable is not deleted along with menu object. /// @param b pointer to buffer that contains the suffix - /// void SetSelectSuffix(const Buffer &b) { m_selected_suffix = b; } /// Sets custom color of highlighted position /// @param col custom color - /// void HighlightColor(Color color) { m_highlight_color = color; } /// @return state of highlighting - /// bool isHighlighted() { return m_highlight_enabled; } /// Turns on/off highlighting /// @param state state of hihglighting - /// void Highlighting(bool state) { m_highlight_enabled = state; } /// Turns on/off cyclic scrolling /// @param state state of cyclic scrolling - /// void CyclicScrolling(bool state) { m_cyclic_scroll_enabled = state; } /// Turns on/off centered cursor /// @param state state of centered cursor - /// void CenteredCursor(bool state) { m_autocenter_cursor = state; } /// Checks if list is empty /// @return true if list is empty, false otherwise /// @see ReallyEmpty() - /// virtual bool Empty() const { return m_options_ptr->empty(); } /// Checks if list is really empty since Empty() may not /// be accurate if filter is set) /// @return true if list is empty, false otherwise /// @see Empty() - /// virtual bool ReallyEmpty() const { return m_options.empty(); } /// @return size of the list - /// virtual size_t Size() const; /// @return currently drawn item. The result is defined only within /// drawing function that is called by Refresh() /// @see Refresh() - /// const Item &Drawn() const { return *(*m_options_ptr)[m_drawn_position]; } /// @return position of currently drawn item. The result is defined /// only within drawing function that is called by Refresh() /// @see Refresh() - /// size_t DrawnPosition() const { return m_drawn_position; } /// @return reference to last item on the list /// @throw List::InvalidItem if requested item is separator - /// Menu::Item &Back(); /// @return const reference to last item on the list /// @throw List::InvalidItem if requested item is separator - /// const Menu::Item &Back() const; /// @return reference to curently highlighted object /// @throw List::InvalidItem if requested item is separator - /// Menu::Item &Current(); /// @return const reference to curently highlighted object /// @throw List::InvalidItem if requested item is separator - /// const Menu::Item &Current() const; /// @param pos requested position /// @return reference to item at given position /// @throw std::out_of_range if given position is out of range /// @throw List::InvalidItem if requested item is separator - /// Menu::Item &at(size_t pos); /// @param pos requested position /// @return const reference to item at given position /// @throw std::out_of_range if given position is out of range /// @throw List::InvalidItem if requested item is separator - /// const Menu::Item &at(size_t pos) const; /// @param pos requested position /// @return const reference to item at given position /// @throw List::InvalidItem if requested item is separator - /// const Menu::Item &operator[](size_t pos) const; /// @param pos requested position /// @return const reference to item at given position /// @throw List::InvalidItem if requested item is separator - /// Menu::Item &operator[](size_t pos); Iterator Begin() { return Iterator(m_options_ptr->begin()); } @@ -515,20 +421,15 @@ template struct Menu : public Window, public List ConstReverseValueIterator RendV() const { return ConstReverseValueIterator(BeginV()); } private: - /// Clears filter, filtered data etc. - /// - void clearFiltered(); - bool isHighlightable(size_t pos) { return !(*m_options_ptr)[pos]->isSeparator() && !(*m_options_ptr)[pos]->isInactive(); } ItemDisplayer m_item_displayer; - ItemStringifier m_item_stringifier; FilterFunction m_filter; - std::string m_search_constraint; + FilterFunction m_searcher; std::vector *m_options_ptr; std::vector m_options; @@ -551,11 +452,6 @@ private: Buffer m_selected_suffix; }; -/// Specialization for T = std::string. It's obvious that if strings are stored, -/// we don't need extra function to convert them to strings by default -template <> std::string Menu::GetItem(size_t pos); -template <> std::string Menu::Stringify(const Menu::Item &item) const; - template Menu::Menu(size_t startx, size_t starty, size_t width, @@ -565,7 +461,6 @@ template Menu::Menu(size_t startx, Border border) : Window(startx, starty, width, height, title, color, border), m_item_displayer(0), - m_item_stringifier(0), m_options_ptr(&m_options), m_beginning(0), m_highlight(0), @@ -806,18 +701,11 @@ template void Menu::Reset() m_beginning = 0; } -template void Menu::clearFiltered() -{ - m_filtered_options.clear(); - m_filtered_positions.clear(); - m_options_ptr = &m_options; -} - template void Menu::Clear() { for (auto it = m_options.begin(); it != m_options.end(); ++it) delete *it; - clearFiltered(); + clearFilterResults(); m_options.clear(); m_found_positions.clear(); m_options_ptr = &m_options; @@ -858,15 +746,15 @@ template size_t Menu::Choice() const return m_highlight; } -template template -void Menu::Filter(Iterator_ first, Iterator_ last, const FilterFunction &f) +template +void Menu::filter(ConstIterator first, ConstIterator last, const FilterFunction &f) { assert(m_options_ptr != &m_filtered_options); - clearFiltered(); + clearFilterResults(); m_filter = f; for (auto it = first; it != last; ++it) { - if (m_filter(*this, *it)) + if (m_filter(*it)) { size_t pos = it-Begin(); m_filtered_positions.push_back(pos); @@ -876,6 +764,34 @@ void Menu::Filter(Iterator_ first, Iterator_ last, const FilterFunction &f) m_options_ptr = &m_filtered_options; } +template void Menu::clearFilterResults() +{ + m_filtered_options.clear(); + m_filtered_positions.clear(); + m_options_ptr = &m_options; +} + +template +bool Menu::search(ConstIterator first, ConstIterator last, const FilterFunction &f) +{ + m_found_positions.clear(); + m_searcher = f; + for (auto it = first; it != last; ++it) + { + if (m_searcher(*it)) + { + size_t pos = it-Begin(); + m_found_positions.insert(pos); + } + } + return !m_found_positions.empty(); +} + +template void Menu::clearSearchResults() +{ + m_found_positions.clear(); +} + template void Menu::ReverseSelection(size_t beginning) { auto it = m_options_ptr->begin()+beginning; @@ -883,30 +799,11 @@ template void Menu::ReverseSelection(size_t beginning) (*it)->setSelected(!(*it)->isSelected() && !(*it)->isInactive()); } -template bool Menu::Search(const std::string &constraint, size_t beginning, int flags) -{ - m_found_positions.clear(); - m_search_constraint.clear(); - if (constraint.empty()) - return false; - m_search_constraint = constraint; - Regex rx; - if (rx.compile(m_search_constraint, flags)) - { - for (size_t i = beginning; i < m_options_ptr->size(); ++i) - { - if (rx.match(GetItem(i))) - m_found_positions.insert(i); - } - } - return !m_found_positions.empty(); -} - template void Menu::NextFound(bool wrap) { if (m_found_positions.empty()) return; - std::set::iterator next = m_found_positions.upper_bound(m_highlight); + auto next = m_found_positions.upper_bound(m_highlight); if (next != m_found_positions.end()) Highlight(*next); else if (wrap) @@ -917,29 +814,13 @@ template void Menu::PrevFound(bool wrap) { if (m_found_positions.empty()) return; - std::set::iterator prev = m_found_positions.lower_bound(m_highlight); + auto prev = m_found_positions.lower_bound(m_highlight); if (prev != m_found_positions.begin()) Highlight(*--prev); else if (wrap) Highlight(*m_found_positions.rbegin()); } -template std::string Menu::GetItem(size_t pos) -{ - std::string result; - if (m_item_stringifier) - result = m_item_stringifier((*m_options_ptr)[pos]->value()); - return result; -} - -template std::string Menu::Stringify(const Item &item) const -{ - std::string result; - if (m_item_stringifier) - result = m_item_stringifier(item.value()); - return result; -} - template typename Menu::Item &Menu::Back() { return *m_options_ptr->back(); diff --git a/src/playlist.cpp b/src/playlist.cpp index 84484937..add812ed 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -26,6 +26,7 @@ #include "helpers.h" #include "menu.h" #include "playlist.h" +#include "regex_filter.h" #include "song.h" #include "status.h" #include "utility/comparators.h" @@ -38,11 +39,18 @@ Playlist *myPlaylist = new Playlist; bool Playlist::ReloadTotalLength = 0; bool Playlist::ReloadRemaining = false; -const size_t Playlist::SortOptions = 10; -const size_t Playlist::SortDialogWidth = 30; -size_t Playlist::SortDialogHeight; +namespace {// -Menu< std::pair > *Playlist::SortDialog = 0; +Menu< std::pair > *SortDialog = 0; +size_t SortDialogHeight; + +const size_t SortOptions = 10; +const size_t SortDialogWidth = 30; + +std::string songToString(const MPD::Song &s); +bool playlistEntryMatcher(const Regex &rx, const MPD::Song &s); + +} void Playlist::Init() { @@ -53,15 +61,9 @@ void Playlist::Init() Items->SetSelectPrefix(Config.selected_item_prefix); Items->SetSelectSuffix(Config.selected_item_suffix); if (Config.columns_in_playlist) - { Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this)); - Items->SetItemStringifier(SongInColumnsToString); - } else - { Items->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format)); - Items->SetItemStringifier(SongToString); - } if (!SortDialog) { @@ -284,6 +286,8 @@ void Playlist::GetSelectedSongs(MPD::SongList &v) v.push_back(Items->at(*it).value()); } +/***********************************************************************/ + std::string Playlist::currentFilter() { std::string filter; @@ -297,11 +301,38 @@ void Playlist::applyFilter(const std::string &filter) if (w == Items) { Items->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Items->Filter(Items->Begin(), Items->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, playlistEntryMatcher); + Items->filter(Items->Begin(), Items->End(), rx); } } +/***********************************************************************/ + +bool Playlist::search(const std::string &constraint) +{ + bool result = false; + if (w == Items) + { + auto rx = RegexFilter(constraint, Config.regex_type, playlistEntryMatcher); + result = Items->search(Items->Begin(), Items->End(), rx); + } + return result; +} + +void Playlist::nextFound(bool wrap) +{ + if (w == Items) + Items->NextFound(wrap); +} + +void Playlist::prevFound(bool wrap) +{ + if (w == Items) + Items->PrevFound(wrap); +} + +/***********************************************************************/ + bool Playlist::isFiltered() { if (Items->isFiltered()) @@ -316,10 +347,6 @@ void Playlist::MoveSelectedItems(Movement where) { if (Items->Empty() || isFiltered()) return; - - // remove search results as we may move them to different positions, but - // search rememebers positions and may point to wrong ones after that. - Items->Search(""); switch (where) { @@ -462,6 +489,11 @@ void Playlist::EnableHighlighting() UpdateTimer(); } +bool Playlist::SortingInProgress() +{ + return w == SortDialog; +} + std::string Playlist::TotalLength() { std::ostringstream result; @@ -516,16 +548,6 @@ const MPD::Song *Playlist::NowPlayingSong() return s; } -std::string Playlist::SongToString(const MPD::Song &s) -{ - return s.toString(Config.song_list_format_dollar_free); -} - -std::string Playlist::SongInColumnsToString(const MPD::Song &s) -{ - return s.toString(Config.song_in_columns_to_string_format); -} - bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play, int position) { if (Config.ncmpc_like_songs_adding && in_playlist) @@ -631,3 +653,22 @@ bool Playlist::checkForSong (const MPD::Song &s) return true; return false; } + +namespace {// + +std::string songToString(const MPD::Song &s) +{ + std::string result; + if (Config.columns_in_playlist) + result = s.toString(Config.song_in_columns_to_string_format); + else + result = s.toString(Config.song_list_format_dollar_free); + return result; +} + +bool playlistEntryMatcher(const Regex &rx, const MPD::Song &s) +{ + return rx.match(songToString(s)); +} + +} diff --git a/src/playlist.h b/src/playlist.h index 59018bcd..4610721b 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -26,7 +26,7 @@ #include "screen.h" #include "song.h" -class Playlist : public Screen, public Filterable +class Playlist : public Screen, public Filterable, public Searchable { public: enum Movement { mUp, mDown }; @@ -51,9 +51,15 @@ class Playlist : public Screen, public Filterable virtual void ReverseSelection() { Items->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual List *GetList() { return w == Items ? Items : 0; } virtual bool isMergable() { return true; } @@ -67,7 +73,7 @@ class Playlist : public Screen, public Filterable void Sort(); void Reverse(); void AdjustSortOrder(Movement where); - bool SortingInProgress() { return w == SortDialog; } + bool SortingInProgress(); void EnableHighlighting(); void UpdateTimer() { time(&itsTimer); } @@ -81,8 +87,8 @@ class Playlist : public Screen, public Filterable bool checkForSong(const MPD::Song &s); - static std::string SongToString(const MPD::Song &s); - static std::string SongInColumnsToString(const MPD::Song &s); + //static std::string SongToString(const MPD::Song &s); + //static std::string SongInColumnsToString(const MPD::Song &s); Menu< MPD::Song > *Items; @@ -97,7 +103,6 @@ class Playlist : public Screen, public Filterable private: std::string TotalLength(); - std::string itsBufferedStats; size_t itsTotalLength; @@ -105,11 +110,6 @@ class Playlist : public Screen, public Filterable size_t itsScrollBegin; time_t itsTimer; - - static Menu< std::pair > *SortDialog; - static const size_t SortOptions; - static const size_t SortDialogWidth; - static size_t SortDialogHeight; }; extern Playlist *myPlaylist; diff --git a/src/playlist_editor.cpp b/src/playlist_editor.cpp index 2a8f74cb..380e36a4 100644 --- a/src/playlist_editor.cpp +++ b/src/playlist_editor.cpp @@ -37,10 +37,18 @@ using Global::MainStartY; PlaylistEditor *myPlaylistEditor = new PlaylistEditor; -size_t PlaylistEditor::LeftColumnStartX; -size_t PlaylistEditor::LeftColumnWidth; -size_t PlaylistEditor::RightColumnStartX; -size_t PlaylistEditor::RightColumnWidth; +namespace {// + +size_t LeftColumnStartX; +size_t LeftColumnWidth; +size_t RightColumnStartX; +size_t RightColumnWidth; + +std::string SongToString(const MPD::Song &s); +bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist); +bool SongEntryMatcher(const Regex &rx, const MPD::Song &s); + +} void PlaylistEditor::Init() { @@ -61,15 +69,9 @@ void PlaylistEditor::Init() Content->SetSelectPrefix(Config.selected_item_prefix); Content->SetSelectSuffix(Config.selected_item_suffix); if (Config.columns_in_playlist_editor) - { Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this)); - Content->SetItemStringifier(Playlist::SongInColumnsToString); - } else - { Content->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format)); - Content->SetItemStringifier(Playlist::SongToString); - } w = Playlists; isInitialized = 1; @@ -190,7 +192,7 @@ void PlaylistEditor::MoveSelectedItems(Playlist::Movement where) // remove search results as we may move them to different positions, but // search rememebers positions and may point to wrong ones after that. - Content->Search(""); + Content->clearSearchResults(); switch (where) { @@ -416,6 +418,8 @@ void PlaylistEditor::GetSelectedSongs(MPD::SongList &v) v.push_back(Content->at(*it).value()); } +/***********************************************************************/ + std::string PlaylistEditor::currentFilter() { std::string filter; @@ -431,17 +435,53 @@ void PlaylistEditor::applyFilter(const std::string &filter) if (w == Playlists) { Playlists->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Playlists->Filter(Playlists->Begin(), Playlists->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, PlaylistEntryMatcher); + Playlists->filter(Playlists->Begin(), Playlists->End(), rx); } else if (w == Content) { Content->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Content->Filter(Content->Begin(), Content->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, SongEntryMatcher); + Content->filter(Content->Begin(), Content->End(), rx); } } +/***********************************************************************/ + +bool PlaylistEditor::search(const std::string &constraint) +{ + bool result = false; + if (w == Playlists) + { + auto rx = RegexFilter(constraint, Config.regex_type, PlaylistEntryMatcher); + result = Playlists->search(Playlists->Begin(), Playlists->End(), rx); + } + else if (w == Content) + { + auto rx = RegexFilter(constraint, Config.regex_type, SongEntryMatcher); + result = Content->search(Content->Begin(), Content->End(), rx); + } + return result; +} + +void PlaylistEditor::nextFound(bool wrap) +{ + if (w == Playlists) + Playlists->NextFound(wrap); + else if (w == Content) + Content->NextFound(wrap); +} + +void PlaylistEditor::prevFound(bool wrap) +{ + if (w == Playlists) + Playlists->PrevFound(wrap); + else if (w == Content) + Content->PrevFound(wrap); +} + +/***********************************************************************/ + void PlaylistEditor::Locate(const std::string &name) { if (!isInitialized) @@ -466,5 +506,32 @@ List *PlaylistEditor::GetList() else if (w == Content) return Content; else // silence compiler + { assert(false); + return 0; + } +} + +namespace {// + +std::string SongToString(const MPD::Song &s) +{ + std::string result; + if (Config.columns_in_playlist_editor) + result = s.toString(Config.song_in_columns_to_string_format); + else + result = s.toString(Config.song_list_format_dollar_free); + return result; +} + +bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist) +{ + return rx.match(playlist); +} + +bool SongEntryMatcher(const Regex &rx, const MPD::Song &s) +{ + return rx.match(SongToString(s)); +} + } diff --git a/src/playlist_editor.h b/src/playlist_editor.h index f9116a1e..98d2dc62 100644 --- a/src/playlist_editor.h +++ b/src/playlist_editor.h @@ -24,7 +24,7 @@ #include "playlist.h" #include "ncmpcpp.h" -class PlaylistEditor : public Screen, public Filterable +class PlaylistEditor : public Screen, public Filterable, public Searchable { public: virtual void SwitchTo(); @@ -47,9 +47,15 @@ class PlaylistEditor : public Screen, public Filterable virtual void ReverseSelection() { Content->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); - + + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual void Locate(const std::string &); virtual List *GetList(); @@ -73,11 +79,6 @@ class PlaylistEditor : public Screen, public Filterable private: void AddToPlaylist(bool); - - static size_t LeftColumnStartX; - static size_t LeftColumnWidth; - static size_t RightColumnStartX; - static size_t RightColumnWidth; }; extern PlaylistEditor *myPlaylistEditor; diff --git a/src/regex_filter.h b/src/regex_filter.h index a05b3954..10a0f686 100644 --- a/src/regex_filter.h +++ b/src/regex_filter.h @@ -26,20 +26,18 @@ template struct RegexFilter { typedef NCurses::Menu MenuT; - typedef typename NCurses::Menu::Item MenuItem; - typedef std::function FilterFunction; + typedef typename NCurses::Menu::Item Item; + typedef std::function FilterFunction; - RegexFilter(const std::string ®ex_, int cflags, FilterFunction custom_filter = 0) - : m_rx(regex_, cflags), m_custom_filter(custom_filter) { } + RegexFilter(const std::string ®ex_, int cflags, FilterFunction filter) + : m_rx(regex_, cflags), m_filter(filter) { } - bool operator()(MenuT &menu, const MenuItem &item) { + bool operator()(const Item &item) { if (m_rx.regex().empty()) return true; if (!m_rx.error().empty()) return false; - if (m_custom_filter) - return m_custom_filter(m_rx, menu, item); - return m_rx.match(menu.Stringify(item)); + return m_filter(m_rx, item.value()); } static std::string currentFilter(MenuT &menu) @@ -53,7 +51,38 @@ template struct RegexFilter private: Regex m_rx; - FilterFunction m_custom_filter; + FilterFunction m_filter; +}; + +template struct RegexItemFilter +{ + typedef NCurses::Menu MenuT; + typedef typename NCurses::Menu::Item Item; + typedef std::function FilterFunction; + + RegexItemFilter(const std::string ®ex_, int cflags, FilterFunction filter) + : m_rx(regex_, cflags), m_filter(filter) { } + + bool operator()(const Item &item) { + if (m_rx.regex().empty()) + return true; + if (!m_rx.error().empty()) + return false; + return m_filter(m_rx, item); + } + + static std::string currentFilter(MenuT &menu) + { + std::string filter; + auto rf = menu.getFilter().template target< RegexItemFilter >(); + if (rf) + filter = rf->m_rx.regex(); + return filter; + } + +private: + Regex m_rx; + FilterFunction m_filter; }; #endif diff --git a/src/search_engine.cpp b/src/search_engine.cpp index e95b3919..3b29ab61 100644 --- a/src/search_engine.cpp +++ b/src/search_engine.cpp @@ -18,12 +18,14 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#include #include #include "display.h" #include "global.h" #include "helpers.h" #include "playlist.h" +#include "regex_filter.h" #include "search_engine.h" #include "settings.h" #include "status.h" @@ -34,6 +36,40 @@ using Global::MainStartY; SearchEngine *mySearcher = new SearchEngine; +namespace {// + +/*const std::array constraintsNames = {{ + "Any", + "Artist", + "Album Artist", + "Title", + "Album", + "Filename", + "Composer", + "Performer", + "Genre", + "Date", + "Comment" +}}; + +const std::array searchModes = {{ + "Match if tag contains searched phrase (no regexes)", + "Match if tag contains searched phrase (regexes supported)", + "Match only if both values are the same" +}}; + +namespace pos {// + const size_t searchIn = constraintsNames.size()-1+1+1; // separated + const size_t searchMode = searchIn+1; + const size_t search = searchMode+1+1; // separated + const size_t reset = search+1; +}*/ + +std::string SEItemToString(const SEItem &ei); +bool SEItemEntryMatcher(const Regex &rx, const Menu::Item &item, bool filter); + +} + const char *SearchEngine::ConstraintsNames[] = { "Any", @@ -70,7 +106,6 @@ void SearchEngine::Init() w->setItemDisplayer(Display::SearchEngine); w->SetSelectPrefix(Config.selected_item_prefix); w->SetSelectSuffix(Config.selected_item_suffix); - w->SetItemStringifier(SearchEngineOptionToString); SearchMode = &SearchModes[Config.search_engine_default_search_mode]; isInitialized = 1; } @@ -261,23 +296,42 @@ void SearchEngine::GetSelectedSongs(MPD::SongList &v) } } +/***********************************************************************/ + std::string SearchEngine::currentFilter() { - return RegexFilter::currentFilter(*w); + return RegexItemFilter::currentFilter(*w); } void SearchEngine::applyFilter(const std::string &filter) { w->ShowAll(); - auto fun = [](const Regex &rx, Menu &menu, const Menu::Item &item) { - if (item.isSeparator() || !item.value().isSong()) - return true; - return rx.match(menu.Stringify(item)); - }; - auto rx = RegexFilter(filter, Config.regex_type, fun); - w->Filter(w->Begin(), w->End(), rx); + auto fun = std::bind(SEItemEntryMatcher, _1, _2, true); + auto rx = RegexItemFilter(filter, Config.regex_type, fun); + w->filter(w->Begin(), w->End(), rx); } +/***********************************************************************/ + +bool SearchEngine::search(const std::string &constraint) +{ + auto fun = std::bind(SEItemEntryMatcher, _1, _2, false); + auto rx = RegexItemFilter(constraint, Config.regex_type, fun); + return w->search(w->Begin(), w->End(), rx); +} + +void SearchEngine::nextFound(bool wrap) +{ + w->NextFound(wrap); +} + +void SearchEngine::prevFound(bool wrap) +{ + w->PrevFound(wrap); +} + +/***********************************************************************/ + void SearchEngine::UpdateFoundList() { bool bold = 0; @@ -525,15 +579,28 @@ void SearchEngine::Search() } } -std::string SearchEngine::SearchEngineOptionToString(const SEItem &ei) +namespace {// + +std::string SEItemToString(const SEItem &ei) { std::string result; if (ei.isSong()) { if (Config.columns_in_search_engine) - result = Playlist::SongInColumnsToString(ei.song()); + result = ei.song().toString(Config.song_in_columns_to_string_format); else result = ei.song().toString(Config.song_list_format_dollar_free); } + else + result = ei.buffer().Str(); return result; } + +bool SEItemEntryMatcher(const Regex &rx, const Menu::Item &item, bool filter) +{ + if (item.isSeparator() || !item.value().isSong()) + return filter; + return rx.match(SEItemToString(item.value())); +} + +} diff --git a/src/search_engine.h b/src/search_engine.h index 134d884c..7b9ed8f9 100644 --- a/src/search_engine.h +++ b/src/search_engine.h @@ -23,7 +23,6 @@ #include -#include "regex_filter.h" #include "interfaces.h" #include "mpdpp.h" #include "ncmpcpp.h" @@ -74,7 +73,7 @@ struct SEItem MPD::Song itsSong; }; -class SearchEngine : public Screen< Menu >, public Filterable +class SearchEngine : public Screen< Menu >, public Filterable, public Searchable { public: virtual void Resize(); @@ -94,9 +93,15 @@ class SearchEngine : public Screen< Menu >, public Filterable virtual void ReverseSelection() { w->ReverseSelection(StaticOptions); } virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual List *GetList() { return w->Size() >= StaticOptions ? w : 0; } virtual bool isMergable() { return true; } @@ -118,8 +123,6 @@ class SearchEngine : public Screen< Menu >, public Filterable const char **SearchMode; - static std::string SearchEngineOptionToString(const SEItem &); - static const char *SearchModes[]; static const size_t ConstraintsNumber = 11; diff --git a/src/status.cpp b/src/status.cpp index 47bf5771..0847c9a0 100644 --- a/src/status.cpp +++ b/src/status.cpp @@ -227,6 +227,8 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *) if (!np.empty()) WindowTitle(np.toString(Config.song_window_title_format)); + myPlaylist->Items->clearSearchResults(); + bool is_filtered = myPlaylist->Items->isFiltered(); myPlaylist->Items->ShowAll(); @@ -302,7 +304,7 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *) if (myLibrary->Columns() == 2) myLibrary->Albums->Clear(); else - myLibrary->Artists->Clear(); + myLibrary->Tags->Clear(); } if (myPlaylistEditor->Main()) myPlaylistEditor->Content->Clear(); diff --git a/src/tag_editor.cpp b/src/tag_editor.cpp index fbe329ce..fa2cd43e 100644 --- a/src/tag_editor.cpp +++ b/src/tag_editor.cpp @@ -47,22 +47,47 @@ using Global::MainStartY; TagEditor *myTagEditor = new TagEditor; -std::string TagEditor::PatternsFile = "patterns.list"; -std::list TagEditor::Patterns; +namespace {// -size_t TagEditor::LeftColumnWidth; -size_t TagEditor::LeftColumnStartX; -size_t TagEditor::MiddleColumnWidth; -size_t TagEditor::MiddleColumnStartX; -size_t TagEditor::RightColumnWidth; -size_t TagEditor::RightColumnStartX; +size_t LeftColumnWidth; +size_t LeftColumnStartX; +size_t MiddleColumnWidth; +size_t MiddleColumnStartX; +size_t RightColumnWidth; +size_t RightColumnStartX; -size_t TagEditor::FParserDialogWidth; -size_t TagEditor::FParserDialogHeight; -size_t TagEditor::FParserWidth; -size_t TagEditor::FParserWidthOne; -size_t TagEditor::FParserWidthTwo; -size_t TagEditor::FParserHeight; +size_t FParserDialogWidth; +size_t FParserDialogHeight; +size_t FParserWidth; +size_t FParserWidthOne; +size_t FParserWidthTwo; +size_t FParserHeight; + +std::list Patterns; +std::string PatternsFile = "patterns.list"; + +std::string CapitalizeFirstLetters(const std::string &s); +void CapitalizeFirstLetters(MPD::MutableSong &s); +void LowerAllLetters(MPD::MutableSong &s); +void GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f); + +template +void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list); +void WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag); + +void GetPatternList(); +void SavePatternList(); + +MPD::MutableSong::SetFunction IntoSetFunction(char c); +std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern); +std::string ParseFilename(MPD::MutableSong &s, std::string mask, bool preview); + +std::string SongToString(const MPD::MutableSong &s); +bool DirEntryMatcher(const Regex &rx, const string_pair &dir, bool filter); +bool AlbumEntryMatcher(const Regex &rx, const string_pair &dir); +bool SongEntryMatcher(const Regex &rx, const MPD::MutableSong &s); + +} void TagEditor::Init() { @@ -74,14 +99,12 @@ void TagEditor::Init() Albums->CyclicScrolling(Config.use_cyclic_scrolling); Albums->CenteredCursor(Config.centered_cursor); Albums->setItemDisplayer(Display::Pair); - Albums->SetItemStringifier(StringPairToString); Dirs = new Menu(0, MainStartY, LeftColumnWidth, MainHeight, Config.titles_visibility ? "Directories" : "", Config.main_color, brNone); Dirs->HighlightColor(Config.active_column_color); Dirs->CyclicScrolling(Config.use_cyclic_scrolling); Dirs->CenteredCursor(Config.centered_cursor); Dirs->setItemDisplayer(Display::Pair); - Dirs->SetItemStringifier(StringPairToString); LeftColumn = Config.albums_in_tag_editor ? Albums : Dirs; @@ -112,7 +135,6 @@ void TagEditor::Init() Tags->SetSelectPrefix(Config.selected_item_prefix); Tags->SetSelectSuffix(Config.selected_item_suffix); Tags->setItemDisplayer(Display::Tags); - Tags->SetItemStringifier(TagToString); FParserDialog = new Menu((COLS-FParserDialogWidth)/2, (MainHeight-FParserDialogHeight)/2+MainStartY, FParserDialogWidth, FParserDialogHeight, "", Config.main_color, Config.window_border); FParserDialog->CyclicScrolling(Config.use_cyclic_scrolling); @@ -772,6 +794,8 @@ void TagEditor::GetSelectedSongs(MPD::SongList &v) v.push_back(static_cast((*Tags)[*it].value())); } +/***********************************************************************/ + std::string TagEditor::currentFilter() { std::string filter; @@ -789,28 +813,70 @@ void TagEditor::applyFilter(const std::string &filter) if (w == Dirs) { Dirs->ShowAll(); - auto fun = [](const Regex &rx, Menu &menu, const Menu::Item &item) { - if (item.value().first == "." || item.value().first == "..") - return true; - return rx.match(menu.Stringify(item)); - }; + auto fun = std::bind(DirEntryMatcher, _1, _2, true); auto rx = RegexFilter(filter, Config.regex_type, fun); - Dirs->Filter(Dirs->Begin(), Dirs->End(), rx); + Dirs->filter(Dirs->Begin(), Dirs->End(), rx); } else if (w == Albums) { Albums->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Albums->Filter(Albums->Begin(), Albums->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, AlbumEntryMatcher); + Albums->filter(Albums->Begin(), Albums->End(), rx); } else if (w == Tags) { Tags->ShowAll(); - auto rx = RegexFilter(filter, Config.regex_type); - Tags->Filter(Tags->Begin(), Tags->End(), rx); + auto rx = RegexFilter(filter, Config.regex_type, SongEntryMatcher); + Tags->filter(Tags->Begin(), Tags->End(), rx); } } +/***********************************************************************/ + +bool TagEditor::search(const std::string &constraint) +{ + bool result = false; + if (w == Dirs) + { + auto fun = std::bind(DirEntryMatcher, _1, _2, false); + auto rx = RegexFilter(constraint, Config.regex_type, fun); + result = Dirs->search(Dirs->Begin(), Dirs->End(), rx); + } + else if (w == Albums) + { + auto rx = RegexFilter(constraint, Config.regex_type, AlbumEntryMatcher); + result = Albums->search(Albums->Begin(), Albums->End(), rx); + } + else if (w == Tags) + { + auto rx = RegexFilter(constraint, Config.regex_type, SongEntryMatcher); + result = Tags->search(Tags->Begin(), Tags->End(), rx); + } + return result; +} + +void TagEditor::nextFound(bool wrap) +{ + if (w == Dirs) + Dirs->NextFound(wrap); + else if (w == Albums) + Albums->NextFound(wrap); + else if (w == Tags) + Tags->NextFound(wrap); +} + +void TagEditor::prevFound(bool wrap) +{ + if (w == Dirs) + Dirs->PrevFound(wrap); + else if (w == Albums) + Albums->PrevFound(wrap); + else if (w == Tags) + Tags->PrevFound(wrap); +} + +/***********************************************************************/ + List *TagEditor::GetList() { if (w == LeftColumn) @@ -1008,40 +1074,6 @@ void TagEditor::ReadTags(MPD::MutableSong &s) s.setComment(f.tag()->comment().to8Bit(1)); } -namespace -{ - template void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list) - { - using TagLib::ID3v2::TextIdentificationFrame; - tag->removeFrames(type); - TextIdentificationFrame *frame = new TextIdentificationFrame(type, TagLib::String::UTF8); - frame->setText(list); - tag->addFrame(frame); - } -} - -void TagEditor::WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag) -{ - TagLib::StringList list; - - tag->addField("DISCNUMBER", ToWString(s.getDisc())); // disc - - tag->removeField("ALBUM ARTIST"); // album artist - GetTagList(list, s, &MPD::Song::getAlbumArtist); - for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) - tag->addField("ALBUM ARTIST", *it, 0); - - tag->removeField("COMPOSER"); // composer - GetTagList(list, s, &MPD::Song::getComposer); - for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) - tag->addField("COMPOSER", *it, 0); - - tag->removeField("PERFORMER"); // performer - GetTagList(list, s, &MPD::Song::getPerformer); - for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) - tag->addField("PERFORMER", *it, 0); -} - bool TagEditor::WriteTags(MPD::MutableSong &s) { std::string path_to_file; @@ -1129,7 +1161,9 @@ bool TagEditor::WriteTags(MPD::MutableSong &s) return false; } -std::string TagEditor::CapitalizeFirstLetters(const std::string &s) +namespace {// + +std::string CapitalizeFirstLetters(const std::string &s) { if (s.empty()) return ""; @@ -1144,7 +1178,7 @@ std::string TagEditor::CapitalizeFirstLetters(const std::string &s) return result; } -void TagEditor::CapitalizeFirstLetters(MPD::MutableSong &s) +void CapitalizeFirstLetters(MPD::MutableSong &s) { for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m) { @@ -1154,7 +1188,7 @@ void TagEditor::CapitalizeFirstLetters(MPD::MutableSong &s) } } -void TagEditor::LowerAllLetters(MPD::MutableSong &s) +void LowerAllLetters(MPD::MutableSong &s) { for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m) { @@ -1167,7 +1201,7 @@ void TagEditor::LowerAllLetters(MPD::MutableSong &s) } } -void TagEditor::GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f) +void GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f) { list.clear(); unsigned pos = 0; @@ -1175,18 +1209,38 @@ void TagEditor::GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, list.append(ToWString(value)); } -std::string TagEditor::TagToString(const MPD::MutableSong &s) +template void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list) { - std::string result; - size_t i = myTagEditor->TagTypes->Choice(); - if (i < 11) - result = (s.*SongInfo::Tags[i].Get)(0); - else if (i == 12) - result = s.getNewURI().empty() ? s.getName() : s.getName() + " -> " + s.getNewURI(); - return result.empty() ? Config.empty_tag : result; + using TagLib::ID3v2::TextIdentificationFrame; + tag->removeFrames(type); + TextIdentificationFrame *frame = new TextIdentificationFrame(type, TagLib::String::UTF8); + frame->setText(list); + tag->addFrame(frame); } -void TagEditor::GetPatternList() +void WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag) +{ + TagLib::StringList list; + + tag->addField("DISCNUMBER", ToWString(s.getDisc())); // disc + + tag->removeField("ALBUM ARTIST"); // album artist + GetTagList(list, s, &MPD::Song::getAlbumArtist); + for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) + tag->addField("ALBUM ARTIST", *it, 0); + + tag->removeField("COMPOSER"); // composer + GetTagList(list, s, &MPD::Song::getComposer); + for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) + tag->addField("COMPOSER", *it, 0); + + tag->removeField("PERFORMER"); // performer + GetTagList(list, s, &MPD::Song::getPerformer); + for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it) + tag->addField("PERFORMER", *it, 0); +} + +void GetPatternList() { if (Patterns.empty()) { @@ -1197,12 +1251,12 @@ void TagEditor::GetPatternList() while (getline(input, line)) if (!line.empty()) Patterns.push_back(line); - input.close(); + input.close(); } } } -void TagEditor::SavePatternList() +void SavePatternList() { std::ofstream output(PatternsFile.c_str()); if (output.is_open()) @@ -1213,8 +1267,7 @@ void TagEditor::SavePatternList() output.close(); } } - -MPD::MutableSong::SetFunction TagEditor::IntoSetFunction(char c) +MPD::MutableSong::SetFunction IntoSetFunction(char c) { switch (c) { @@ -1245,14 +1298,14 @@ MPD::MutableSong::SetFunction TagEditor::IntoSetFunction(char c) } } -std::string TagEditor::GenerateFilename(const MPD::MutableSong &s, const std::string &pattern) +std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern) { std::string result = s.toString(pattern); removeInvalidCharsFromFilename(result); return result; } -std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool preview) +std::string ParseFilename(MPD::MutableSong &s, std::string mask, bool preview) { std::ostringstream result; std::vector separators; @@ -1268,7 +1321,7 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool separators.push_back(mask.substr(0, i)); } size_t i = 0; - for (std::vector::const_iterator it = separators.begin(); it != separators.end(); ++it, ++i) + for (auto it = separators.begin(); it != separators.end(); ++it, ++i) { size_t j = file.find(*it); tags.at(i).second = file.substr(0, j); @@ -1289,23 +1342,53 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool return "Error while parsing filename!\n"; } - for (std::vector< std::pair >::iterator it = tags.begin(); it != tags.end(); ++it) + for (auto it = tags.begin(); it != tags.end(); ++it) { for (std::string::iterator j = it->second.begin(); j != it->second.end(); ++j) if (*j == '_') *j = ' '; - - if (!preview) - { - MPD::MutableSong::SetFunction set = IntoSetFunction(it->first); - if (set) - s.setTag(set, it->second); - } - else - result << "%" << it->first << ": " << it->second << "\n"; + + if (!preview) + { + MPD::MutableSong::SetFunction set = IntoSetFunction(it->first); + if (set) + s.setTag(set, it->second); + } + else + result << "%" << it->first << ": " << it->second << "\n"; } return result.str(); } +std::string SongToString(const MPD::MutableSong &s) +{ + std::string result; + size_t i = myTagEditor->TagTypes->Choice(); + if (i < 11) + result = (s.*SongInfo::Tags[i].Get)(0); + else if (i == 12) + result = s.getNewURI().empty() ? s.getName() : s.getName() + " -> " + s.getNewURI(); + return result.empty() ? Config.empty_tag : result; +} + +bool DirEntryMatcher(const Regex &rx, const string_pair &dir, bool filter) +{ + if (dir.first == "." || dir.first == "..") + return filter; + return rx.match(dir.first); +} + +bool AlbumEntryMatcher(const Regex &rx, const string_pair &dir) +{ + return rx.match(dir.first); +} + +bool SongEntryMatcher(const Regex &rx, const MPD::MutableSong &s) +{ + return rx.match(SongToString(s)); +} + +} + #endif diff --git a/src/tag_editor.h b/src/tag_editor.h index a9b4b8f1..2e9d26e7 100644 --- a/src/tag_editor.h +++ b/src/tag_editor.h @@ -37,7 +37,7 @@ #include "regex_filter.h" #include "screen.h" -class TagEditor : public Screen, public Filterable +class TagEditor : public Screen, public Filterable, public Searchable { public: TagEditor() : FParser(0), FParserHelper(0), FParserLegend(0), FParserPreview(0), itsBrowsedDir("/") { } @@ -62,9 +62,15 @@ class TagEditor : public Screen, public Filterable virtual void ReverseSelection() { Tags->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); + /// Filterable implementation virtual std::string currentFilter(); virtual void applyFilter(const std::string &filter); + /// Searchable implementation + virtual bool search(const std::string &constraint); + virtual void nextFound(bool wrap); + virtual void prevFound(bool wrap); + virtual List *GetList(); virtual bool isMergable() { return true; } @@ -103,39 +109,8 @@ class TagEditor : public Screen, public Filterable Scrollpad *FParserPreview; bool FParserUsePreview; - static std::string CapitalizeFirstLetters(const std::string &); - static void CapitalizeFirstLetters(MPD::MutableSong &); - static void LowerAllLetters(MPD::MutableSong &); - static void GetTagList(TagLib::StringList &, const MPD::MutableSong &, MPD::Song::GetFunction); - static void WriteXiphComments(const MPD::MutableSong &, TagLib::Ogg::XiphComment *); - - static void GetPatternList(); - static void SavePatternList(); - static MPD::MutableSong::SetFunction IntoSetFunction(char); - static std::string GenerateFilename(const MPD::MutableSong &, const std::string &); - static std::string ParseFilename(MPD::MutableSong &, std::string, bool); - - static std::string TagToString(const MPD::MutableSong &); - std::string itsBrowsedDir; std::string itsHighlightedDir; - - static std::string PatternsFile; - static std::list Patterns; - - static size_t MiddleColumnWidth; - static size_t LeftColumnStartX; - static size_t LeftColumnWidth; - static size_t MiddleColumnStartX; - static size_t RightColumnWidth; - static size_t RightColumnStartX; - - static size_t FParserDialogWidth; - static size_t FParserDialogHeight; - static size_t FParserWidth; - static size_t FParserWidthOne; - static size_t FParserWidthTwo; - static size_t FParserHeight; }; extern TagEditor *myTagEditor;