From de2513a36c527419737be16c565079a07f3b3d6a Mon Sep 17 00:00:00 2001 From: Andrzej Rybczak Date: Sun, 13 Nov 2016 04:37:54 +0100 Subject: [PATCH] Implement filtering in media library --- src/actions.cpp | 12 +- src/browser.cpp | 2 +- src/helpers.h | 22 ++-- src/media_library.cpp | 264 +++++++++++++++++++++++++++--------------- src/media_library.h | 11 +- src/menu_impl.h | 3 +- src/playlist.cpp | 8 +- src/playlist.h | 4 +- src/status.cpp | 4 +- 9 files changed, 211 insertions(+), 119 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 26c634b0..fab61616 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1167,7 +1167,7 @@ void TogglePlayingSongCentering::run() { auto s = myPlaylist->nowPlayingSong(); if (!s.empty()) - myPlaylist->moveToSong(s); + myPlaylist->locateSong(s); } } @@ -1197,7 +1197,7 @@ void JumpToPlayingSong::run() return; if (myScreen == myPlaylist) { - myPlaylist->moveToSong(s); + myPlaylist->locateSong(s); } else if (myScreen == myBrowser) { @@ -1205,7 +1205,7 @@ void JumpToPlayingSong::run() } else if (myScreen == myLibrary) { - myLibrary->LocateSong(s); + myLibrary->locateSong(s); } } @@ -1611,7 +1611,7 @@ bool JumpToMediaLibrary::canBeRun() void JumpToMediaLibrary::run() { - myLibrary->LocateSong(*m_song); + myLibrary->locateSong(*m_song); } bool JumpToPlaylistEditor::canBeRun() @@ -2226,7 +2226,7 @@ void ToggleBrowserSortMode::run() bool ToggleLibraryTagType::canBeRun() { return (myScreen->isActiveWindow(myLibrary->Tags)) - || (myLibrary->Columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums)); + || (myLibrary->columns() == 2 && myScreen->isActiveWindow(myLibrary->Albums)); } void ToggleLibraryTagType::run() @@ -2257,7 +2257,7 @@ void ToggleLibraryTagType::run() std::string and_mtime = Config.media_library_sort_by_mtime ? " and mtime" : ""; - if (myLibrary->Columns() == 2) + if (myLibrary->columns() == 2) { myLibrary->Songs.clear(); myLibrary->Albums.reset(); diff --git a/src/browser.cpp b/src/browser.cpp index 85f60e53..9fc3beb4 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -456,7 +456,7 @@ bool Browser::enterDirectory() void Browser::getDirectory(std::string directory) { - ScopedUnfilteredMenu sunfilter(w); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::Yes, w); m_scroll_beginning = 0; w.clear(); diff --git a/src/helpers.h b/src/helpers.h index 46c5d791..c590f28f 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -34,11 +34,11 @@ enum ReapplyFilter { Yes, No }; -template +template struct ScopedUnfilteredMenu { - ScopedUnfilteredMenu(NC::Menu &menu) - : m_menu(menu) + ScopedUnfilteredMenu(ReapplyFilter reapply_filter, NC::Menu &menu) + : m_refresh(false), m_reapply_filter(reapply_filter), m_menu(menu) { m_is_filtered = m_menu.isFiltered(); if (m_is_filtered) @@ -49,7 +49,7 @@ struct ScopedUnfilteredMenu { if (m_is_filtered) { - switch (reapplyFilter) + switch (m_reapply_filter) { case ReapplyFilter::Yes: m_menu.reapplyFilter(); @@ -59,10 +59,20 @@ struct ScopedUnfilteredMenu break; } } + if (m_refresh) + m_menu.refresh(); + } + + void set(ReapplyFilter reapply_filter, bool refresh) + { + m_reapply_filter = reapply_filter; + m_refresh = refresh; } private: bool m_is_filtered; + bool m_refresh; + ReapplyFilter m_reapply_filter; NC::Menu &m_menu; }; @@ -375,9 +385,7 @@ template std::string getSharedDirectory(Iterator first, Iter template void markSongsInPlaylist(ListT &list) { - ScopedUnfilteredMenu< - typename ListT::Item::Type, - ReapplyFilter::No> sunfilter(list); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::No, list); MPD::Song *s; for (auto &p : static_cast(list)) { diff --git a/src/media_library.cpp b/src/media_library.cpp index 778e1631..64b811eb 100644 --- a/src/media_library.cpp +++ b/src/media_library.cpp @@ -279,9 +279,11 @@ void MediaLibrary::update() { if (hasTwoColumns) { + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); if (Albums.empty() || m_albums_update_request) { m_albums_update_request = false; + sunfilter_albums.set(ReapplyFilter::Yes, true); std::map, time_t> albums; for (MPD::SongIterator s = getDatabaseIterator(Mpd), end; s != end; ++s) { @@ -300,12 +302,11 @@ void MediaLibrary::update() size_t idx = 0; for (const auto &album : albums) { - auto entry = AlbumEntry(Album( - std::move(std::get<0>(album.first)), - std::move(std::get<1>(album.first)), - std::move(std::get<2>(album.first)), - album.second) - ); + auto entry = AlbumEntry( + Album(std::move(std::get<0>(album.first)), + std::move(std::get<1>(album.first)), + std::move(std::get<2>(album.first)), + album.second)); if (idx < Albums.size()) Albums[idx].value() = std::move(entry); else @@ -315,106 +316,112 @@ void MediaLibrary::update() if (idx < Albums.size()) Albums.resizeList(idx); std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries()); - Albums.refresh(); } } else { - if (Tags.empty() || m_tags_update_request) { - m_tags_update_request = false; - std::map tags; - if (Config.media_library_sort_by_mtime) + ScopedUnfilteredMenu sunfilter_tags(ReapplyFilter::No, Tags); + if (Tags.empty() || m_tags_update_request) { - for (MPD::SongIterator s = getDatabaseIterator(Mpd), end; s != end; ++s) + m_tags_update_request = false; + sunfilter_tags.set(ReapplyFilter::Yes, true); + std::map tags; + if (Config.media_library_sort_by_mtime) { - std::string tag; - unsigned idx = 0; - while (!(tag = s->get(Config.media_lib_primary_tag, idx++)).empty()) + for (MPD::SongIterator s = getDatabaseIterator(Mpd), end; s != end; ++s) { - auto it = tags.find(tag); - if (it == tags.end()) - tags[std::move(tag)] = s->getMTime(); - else - it->second = std::max(it->second, s->getMTime()); + std::string tag; + unsigned idx = 0; + while (!(tag = s->get(Config.media_lib_primary_tag, idx++)).empty()) + { + auto it = tags.find(tag); + if (it == tags.end()) + tags[std::move(tag)] = s->getMTime(); + else + it->second = std::max(it->second, s->getMTime()); + } } } - } - else - { - MPD::StringIterator tag = Mpd.GetList(Config.media_lib_primary_tag), end; - for (; tag != end; ++tag) - tags[std::move(*tag)] = 0; - } - size_t idx = 0; - for (const auto &tag : tags) - { - auto ptag = PrimaryTag(std::move(tag.first), tag.second); - if (idx < Tags.size()) - Tags[idx].value() = std::move(ptag); else - Tags.addItem(std::move(ptag)); - ++idx; - } - if (idx < Tags.size()) - Tags.resizeList(idx); - std::sort(Tags.beginV(), Tags.endV(), SortPrimaryTags()); - Tags.refresh(); - } - - if (!Tags.empty() - && ((Albums.empty() && Global::Timer - m_timer > m_fetching_delay) || m_albums_update_request) - ) - { - m_albums_update_request = false; - auto &primary_tag = Tags.current()->value().tag(); - Mpd.StartSearch(true); - Mpd.AddSearch(Config.media_lib_primary_tag, primary_tag); - std::map, time_t> albums; - for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s) - { - auto key = std::make_tuple(s->getAlbum(), s->getDate()); - auto it = albums.find(key); - if (it == albums.end()) - albums[std::move(key)] = s->getMTime(); - else - it->second = std::max(it->second, s->getMTime()); - }; - size_t idx = 0; - for (const auto &album : albums) - { - auto entry = AlbumEntry(Album( - primary_tag, - std::move(std::get<0>(album.first)), - std::move(std::get<1>(album.first)), - album.second) - ); - if (idx < Albums.size()) { - Albums[idx].value() = std::move(entry); - Albums[idx].setSeparator(false); + MPD::StringIterator tag = Mpd.GetList(Config.media_lib_primary_tag), end; + for (; tag != end; ++tag) + tags[std::move(*tag)] = 0; } - else - Albums.addItem(std::move(entry)); - ++idx; + size_t idx = 0; + for (const auto &tag : tags) + { + auto ptag = PrimaryTag(std::move(tag.first), tag.second); + if (idx < Tags.size()) + Tags[idx].value() = std::move(ptag); + else + Tags.addItem(std::move(ptag)); + ++idx; + } + if (idx < Tags.size()) + Tags.resizeList(idx); + std::sort(Tags.beginV(), Tags.endV(), SortPrimaryTags()); } - if (idx < Albums.size()) - Albums.resizeList(idx); - std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries()); - if (albums.size() > 1) + } + + { + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); + if (!Tags.empty() + && ((Albums.empty() && Global::Timer - m_timer > m_fetching_delay) + || m_albums_update_request)) { - Albums.addSeparator(); - Albums.addItem(AlbumEntry::mkAllTracksEntry(primary_tag)); + m_albums_update_request = false; + sunfilter_albums.set(ReapplyFilter::Yes, true); + auto &primary_tag = Tags.current()->value().tag(); + Mpd.StartSearch(true); + Mpd.AddSearch(Config.media_lib_primary_tag, primary_tag); + std::map, time_t> albums; + for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s) + { + auto key = std::make_tuple(s->getAlbum(), s->getDate()); + auto it = albums.find(key); + if (it == albums.end()) + albums[std::move(key)] = s->getMTime(); + else + it->second = std::max(it->second, s->getMTime()); + }; + size_t idx = 0; + for (const auto &album : albums) + { + auto entry = AlbumEntry( + Album(primary_tag, + std::move(std::get<0>(album.first)), + std::move(std::get<1>(album.first)), + album.second)); + if (idx < Albums.size()) + { + Albums[idx].value() = std::move(entry); + Albums[idx].setSeparator(false); + } + else + Albums.addItem(std::move(entry)); + ++idx; + } + if (idx < Albums.size()) + Albums.resizeList(idx); + std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries()); + if (albums.size() > 1) + { + Albums.addSeparator(); + Albums.addItem(AlbumEntry::mkAllTracksEntry(primary_tag)); + } } - Albums.refresh(); } } - + + ScopedUnfilteredMenu sunfilter_songs(ReapplyFilter::No, Songs); if (!Albums.empty() - && ((Songs.empty() && Global::Timer - m_timer > m_fetching_delay) || m_songs_update_request) - ) + && ((Songs.empty() && Global::Timer - m_timer > m_fetching_delay) + || m_songs_update_request)) { m_songs_update_request = false; + sunfilter_songs.set(ReapplyFilter::Yes, true); auto &album = Albums.current()->value(); Mpd.StartSearch(true); Mpd.AddSearch(Config.media_lib_primary_tag, album.entry().tag()); @@ -443,12 +450,13 @@ void MediaLibrary::update() if (idx < Songs.size()) Songs.resizeList(idx); std::sort(Songs.begin(), Songs.end(), SortSongs(!album.isAllTracksEntry())); - Songs.refresh(); } } int MediaLibrary::windowTimeout() { + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); + ScopedUnfilteredMenu sunfilter_songs(ReapplyFilter::No, Songs); if (Albums.empty() || Songs.empty()) return m_window_timeout; else @@ -596,6 +604,68 @@ bool MediaLibrary::search(SearchDirection direction, bool wrap, bool skip_curren return result; } +std::string MediaLibrary::currentFilter() +{ + std::string result; + if (isActiveWindow(Tags)) + { + if (auto pred = Tags.filterPredicate>()) + result = pred->constraint(); + } + else if (isActiveWindow(Albums)) + { + if (auto pred = Albums.filterPredicate>()) + result = pred->constraint(); + } + else if (isActiveWindow(Songs)) + { + if (auto pred = Songs.filterPredicate>()) + result = pred->constraint(); + } + return result; +} + +void MediaLibrary::applyFilter(const std::string &constraint) +{ + if (isActiveWindow(Tags)) + { + if (!constraint.empty()) + { + Tags.applyFilter(Regex::Filter( + constraint, + Config.regex_type, + TagEntryMatcher)); + } + else + Tags.clearFilter(); + } + else if (isActiveWindow(Albums)) + { + if (!constraint.empty()) + { + Albums.applyFilter(Regex::ItemFilter( + constraint, + Config.regex_type, + std::bind(AlbumEntryMatcher, ph::_1, ph::_2, true))); + } + else + Albums.clearFilter(); + } + else if (isActiveWindow(Songs)) + { + if (!constraint.empty()) + { + Songs.applyFilter(Regex::Filter( + constraint, + Config.regex_type, + SongEntryMatcher)); + } + else + Songs.clearFilter(); + + } +} + /***********************************************************************/ bool MediaLibrary::itemAvailable() @@ -701,6 +771,7 @@ std::vector MediaLibrary::getSelectedSongs() } } // if no item is selected, add songs from right column + ScopedUnfilteredMenu sunfilter_songs(ReapplyFilter::No, Songs); if (!any_selected && !Albums.empty()) { size_t begin = result.size(); @@ -724,11 +795,13 @@ bool MediaLibrary::previousColumnAvailable() assert(!hasTwoColumns || !isActiveWindow(Tags)); if (isActiveWindow(Songs)) { - if (!Albums.empty() && (hasTwoColumns || !Tags.empty())) + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); + if (!Albums.empty()) return true; } else if (isActiveWindow(Albums)) { + ScopedUnfilteredMenu sunfilter_tags(ReapplyFilter::No, Tags); if (!hasTwoColumns && !Tags.empty()) return true; } @@ -758,11 +831,13 @@ bool MediaLibrary::nextColumnAvailable() assert(!hasTwoColumns || !isActiveWindow(Tags)); if (isActiveWindow(Tags)) { - if (!Albums.empty() && !Songs.empty()) + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); + if (!Albums.empty()) return true; } else if (isActiveWindow(Albums)) { + ScopedUnfilteredMenu sunfilter_songs(ReapplyFilter::No, Songs); if (!Songs.empty()) return true; } @@ -818,7 +893,7 @@ void MediaLibrary::toggleColumnsMode() resize(); } -int MediaLibrary::Columns() +int MediaLibrary::columns() { if (hasTwoColumns) return 2; @@ -833,6 +908,7 @@ void MediaLibrary::toggleSortMode() Config.media_library_sort_by_mtime ? "modification time" : "name"); if (hasTwoColumns) { + ScopedUnfilteredMenu sunfilter_albums(ReapplyFilter::No, Albums); std::sort(Albums.beginV(), Albums.endV(), SortAlbumEntries()); Albums.refresh(); Songs.clear(); @@ -846,6 +922,7 @@ void MediaLibrary::toggleSortMode() } else { + ScopedUnfilteredMenu sunfilter_tags(ReapplyFilter::No, Tags); // if we already have modification times, just resort. otherwise refetch the list. if (!Tags.empty() && Tags[0].value().mtime() > 0) { @@ -853,14 +930,16 @@ void MediaLibrary::toggleSortMode() Tags.refresh(); } else + { Tags.clear(); + } Albums.clear(); Songs.clear(); } update(); } -void MediaLibrary::LocateSong(const MPD::Song &s) +void MediaLibrary::locateSong(const MPD::Song &s) { std::string primary_tag = s.get(Config.media_lib_primary_tag); if (primary_tag.empty()) @@ -884,6 +963,7 @@ void MediaLibrary::LocateSong(const MPD::Song &s) if (!hasTwoColumns) { + Tags.clearFilter(); if (Tags.empty()) update(); @@ -901,7 +981,8 @@ void MediaLibrary::LocateSong(const MPD::Song &s) } Albums.clear(); } - + + Albums.clearFilter(); if (Albums.empty()) update(); @@ -939,6 +1020,7 @@ void MediaLibrary::LocateSong(const MPD::Song &s) MoveToAlbum(Albums, primary_tag, s); } + Songs.clearFilter(); Songs.clear(); update(); diff --git a/src/media_library.h b/src/media_library.h index b7a59390..0218488d 100644 --- a/src/media_library.h +++ b/src/media_library.h @@ -54,7 +54,10 @@ struct MediaLibrary: Screen, HasColumns, HasSongs, Searchable, Tab virtual void setSearchConstraint(const std::string &constraint) OVERRIDE; virtual void clearSearchConstraint() OVERRIDE; virtual bool search(SearchDirection direction, bool wrap, bool skip_current) OVERRIDE; - + + virtual std::string currentFilter() OVERRIDE; + virtual void applyFilter(const std::string &filter) OVERRIDE; + // HasSongs implementation virtual bool itemAvailable() OVERRIDE; virtual bool addItemToPlaylist(bool play) OVERRIDE; @@ -67,11 +70,11 @@ struct MediaLibrary: Screen, HasColumns, HasSongs, Searchable, Tab virtual bool nextColumnAvailable() OVERRIDE; virtual void nextColumn() OVERRIDE; - // private members + // other members void updateTimer(); void toggleColumnsMode(); - int Columns(); - void LocateSong(const MPD::Song &); + int columns(); + void locateSong(const MPD::Song &s); void toggleSortMode(); void requestTagsUpdate() { m_tags_update_request = true; } diff --git a/src/menu_impl.h b/src/menu_impl.h index bec25230..8d58e63b 100644 --- a/src/menu_impl.h +++ b/src/menu_impl.h @@ -336,10 +336,9 @@ void Menu::reset() template void Menu::clear() { - // Don't clear the filter here. + // Don't clear filter related stuff here. m_all_items.clear(); m_filtered_items.clear(); - m_items = &m_all_items; } template diff --git a/src/playlist.cpp b/src/playlist.cpp index d2c9ce91..1f182c85 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -223,7 +223,7 @@ MPD::Song Playlist::nowPlayingSong() MPD::Song s; if (Status::State::player() != MPD::psUnknown) { - ScopedUnfilteredMenu sunfilter(w); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::No, w); auto sp = Status::State::currentSongPosition(); if (sp >= 0 && size_t(sp) < w.size()) s = w.at(sp).value(); @@ -231,7 +231,7 @@ MPD::Song Playlist::nowPlayingSong() return s; } -void Playlist::moveToSong(const MPD::Song &s) +void Playlist::locateSong(const MPD::Song &s) { if (!w.isFiltered()) w.highlight(s.getPosition()); @@ -268,7 +268,7 @@ std::string Playlist::getTotalLength() } if (Config.playlist_show_remaining_time && m_reload_remaining) { - ScopedUnfilteredMenu sunfilter(w); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::No, w); m_remaining_time = 0; for (size_t i = Status::State::currentSongPosition(); i < w.size(); ++i) m_remaining_time += w[i].value().getDuration(); @@ -279,7 +279,7 @@ std::string Playlist::getTotalLength() if (w.isFiltered()) { - ScopedUnfilteredMenu sunfilter(w); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::No, w); result << " (out of " << w.size() << ")"; } diff --git a/src/playlist.h b/src/playlist.h index 585e6441..6810d2f5 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -66,8 +66,8 @@ struct Playlist: Screen, HasSongs, Searchable, Tabbable // other members MPD::Song nowPlayingSong(); - // Move to given song from playlist. - void moveToSong(const MPD::Song &s); + // Locate song in playlist. + void locateSong(const MPD::Song &s); void enableHighlighting(); diff --git a/src/status.cpp b/src/status.cpp index 19472cc6..f0e71a38 100644 --- a/src/status.cpp +++ b/src/status.cpp @@ -409,7 +409,7 @@ int Status::State::volume() void Status::Changes::playlist(unsigned previous_version) { { - ScopedUnfilteredMenu sunfilter(myPlaylist->main()); + ScopedUnfilteredMenu sunfilter(ReapplyFilter::Yes, myPlaylist->main()); if (m_playlist_length < myPlaylist->main().size()) { @@ -580,7 +580,7 @@ void Status::Changes::songID(int song_id) drawTitle(s); if (Config.autocenter_mode) - myPlaylist->moveToSong(s); + myPlaylist->locateSong(s); if (Config.now_playing_lyrics && isVisible(myLyrics) && myLyrics->previousScreen() == myPlaylist) {