{Album,Artist}ListPage: merge common code into a generic class
authorMax Kellermann <max@musicpd.org>
Wed, 10 Oct 2018 14:00:17 +0000 (16:00 +0200)
committerMax Kellermann <max@musicpd.org>
Wed, 10 Oct 2018 14:00:17 +0000 (16:00 +0200)
meson.build
src/ArtistListPage.cxx [deleted file]
src/ArtistListPage.hxx [deleted file]
src/TagFilter.cxx [new file with mode: 0644]
src/TagFilter.hxx [new file with mode: 0644]
src/TagListPage.cxx [moved from src/AlbumListPage.cxx with 62% similarity]
src/TagListPage.hxx [moved from src/AlbumListPage.hxx with 62% similarity]
src/screen_artist.cxx

index d42e471..fea42e7 100644 (file)
@@ -227,8 +227,8 @@ conf.set('ENABLE_ARTIST_SCREEN', enable_artist_screen)
 if enable_artist_screen
   sources += [
     'src/screen_artist.cxx',
-    'src/ArtistListPage.cxx',
-    'src/AlbumListPage.cxx',
+    'src/TagListPage.cxx',
+    'src/TagFilter.cxx',
   ]
 endif
 
diff --git a/src/ArtistListPage.cxx b/src/ArtistListPage.cxx
deleted file mode 100644 (file)
index aa10812..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-/* ncmpc (Ncurses MPD Client)
- * (c) 2004-2018 The Music Player Daemon Project
- * Project homepage: http://musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "ArtistListPage.hxx"
-#include "screen_status.hxx"
-#include "screen_find.hxx"
-#include "FileListPage.hxx"
-#include "Command.hxx"
-#include "i18n.h"
-#include "charset.hxx"
-#include "mpdclient.hxx"
-#include "util/StringUTF8.hxx"
-
-#include <algorithm>
-
-#include <assert.h>
-#include <string.h>
-
-gcc_pure
-static bool
-CompareUTF8(const std::string &a, const std::string &b)
-{
-       return CollateUTF8(a.c_str(), b.c_str()) < 0;
-}
-
-const char *
-ArtistListPage::GetListItemText(char *buffer, size_t size,
-                               unsigned idx) const noexcept
-{
-       assert(idx < artist_list.size());
-
-       return utf8_to_locale(artist_list[idx].c_str(), buffer, size);
-}
-
-static void
-recv_tag_values(struct mpd_connection *connection, enum mpd_tag_type tag,
-               std::vector<std::string> &list)
-{
-       struct mpd_pair *pair;
-
-       while ((pair = mpd_recv_pair_tag(connection, tag)) != nullptr) {
-               list.emplace_back(pair->value);
-               mpd_return_pair(connection, pair);
-       }
-}
-
-void
-ArtistListPage::LoadArtistList(struct mpdclient &c)
-{
-       auto *connection = c.GetConnection();
-
-       artist_list.clear();
-
-       if (connection != nullptr) {
-               mpd_search_db_tags(connection, MPD_TAG_ARTIST);
-               mpd_search_commit(connection);
-               recv_tag_values(connection, MPD_TAG_ARTIST, artist_list);
-
-               c.FinishCommand();
-       }
-
-       /* sort list */
-       std::sort(artist_list.begin(), artist_list.end(), CompareUTF8);
-       lw.SetLength(artist_list.size());
-}
-
-void
-ArtistListPage::Reload(struct mpdclient &c)
-{
-       LoadArtistList(c);
-}
-
-void
-ArtistListPage::PaintListItem(WINDOW *w, unsigned i,
-                             gcc_unused unsigned y, unsigned width,
-                             bool selected) const noexcept
-{
-       screen_browser_paint_directory(w, width, selected,
-                                      Utf8ToLocale(artist_list[i].c_str()).c_str());
-}
-
-void
-ArtistListPage::Paint() const noexcept
-{
-       lw.Paint(*this);
-}
-
-const char *
-ArtistListPage::GetTitle(char *, size_t) const noexcept
-{
-       return _("All artists");
-}
-
-void
-ArtistListPage::Update(struct mpdclient &c, unsigned events) noexcept
-{
-       if (events & MPD_IDLE_DATABASE) {
-               /* the db has changed -> update the list */
-               Reload(c);
-               SetDirty();
-       }
-}
-
-/* add_query - Add all songs satisfying specified criteria */
-static void
-add_query(struct mpdclient *c, enum mpd_tag_type table, const char *_filter)
-{
-       assert(_filter != nullptr);
-
-       auto *connection = c->GetConnection();
-       if (connection == nullptr)
-               return;
-
-       screen_status_printf(_("Adding \'%s\' to queue"),
-                            Utf8ToLocale(_filter).c_str());
-
-       mpd_search_add_db_songs(connection, true);
-       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                     table, _filter);
-       mpd_search_commit(connection);
-       c->FinishCommand();
-}
-
-inline bool
-ArtistListPage::OnListCommand(Command cmd)
-{
-       if (lw.HandleCommand(cmd)) {
-               SetDirty();
-               return true;
-       }
-
-       return false;
-}
-
-bool
-ArtistListPage::OnCommand(struct mpdclient &c, Command cmd)
-{
-       switch(cmd) {
-               const char *selected;
-
-       case Command::SELECT:
-       case Command::ADD:
-               if (lw.selected >= artist_list.size())
-                       return true;
-
-               for (const unsigned i : lw.GetRange()) {
-                       selected = artist_list[i].c_str();
-                       add_query(&c, MPD_TAG_ARTIST, selected);
-                       cmd = Command::LIST_NEXT; /* continue and select next item... */
-               }
-
-               break;
-
-               /* continue and update... */
-       case Command::SCREEN_UPDATE:
-               Reload(c);
-               return false;
-
-       case Command::LIST_FIND:
-       case Command::LIST_RFIND:
-       case Command::LIST_FIND_NEXT:
-       case Command::LIST_RFIND_NEXT:
-               screen_find(screen, lw, cmd, *this);
-               SetDirty();
-               return true;
-
-       case Command::LIST_JUMP:
-               screen_jump(screen, lw, *this, *this);
-               SetDirty();
-               return true;
-
-       default:
-               break;
-       }
-
-       if (OnListCommand(cmd))
-               return true;
-
-       return false;
-}
diff --git a/src/ArtistListPage.hxx b/src/ArtistListPage.hxx
deleted file mode 100644 (file)
index 0e6ca84..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/* ncmpc (Ncurses MPD Client)
- * (c) 2004-2018 The Music Player Daemon Project
- * Project homepage: http://musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#ifndef NCMPC_ARTIST_LIST_PAGE_HXX
-#define NCMPC_ARTIST_LIST_PAGE_HXX
-
-#include "ListPage.hxx"
-#include "ListRenderer.hxx"
-#include "ListText.hxx"
-
-#include <vector>
-#include <string>
-
-class ScreenManager;
-
-class ArtistListPage final : public ListPage, ListRenderer, ListText {
-       ScreenManager &screen;
-       std::vector<std::string> artist_list;
-
-public:
-       ArtistListPage(ScreenManager &_screen, WINDOW *_w, Size _size)
-               :ListPage(_w, _size), screen(_screen) {}
-
-       const char *GetSelectedValue() const {
-               return lw.selected < artist_list.size()
-                       ? artist_list[lw.selected].c_str()
-                       : nullptr;
-       }
-
-private:
-       void LoadArtistList(struct mpdclient &c);
-       void Reload(struct mpdclient &c);
-
-       bool OnListCommand(Command cmd);
-
-public:
-       /* virtual methods from class Page */
-       void Paint() const noexcept override;
-       void Update(struct mpdclient &c, unsigned events) noexcept override;
-       bool OnCommand(struct mpdclient &c, Command cmd) override;
-       const char *GetTitle(char *s, size_t size) const noexcept override;
-
-       /* virtual methods from class ListRenderer */
-       void PaintListItem(WINDOW *w, unsigned i, unsigned y, unsigned width,
-                          bool selected) const noexcept override;
-
-       /* virtual methods from class ListText */
-       const char *GetListItemText(char *buffer, size_t size,
-                                   unsigned i) const noexcept override;
-};
-
-#endif
diff --git a/src/TagFilter.cxx b/src/TagFilter.cxx
new file mode 100644 (file)
index 0000000..e89fc6b
--- /dev/null
@@ -0,0 +1,42 @@
+/* ncmpc (Ncurses MPD Client)
+ * (c) 2004-2018 The Music Player Daemon Project
+ * Project homepage: http://musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "TagFilter.hxx"
+
+#include <mpd/client.h>
+
+const char *
+FindTag(const TagFilter &filter, enum mpd_tag_type tag) noexcept
+{
+       for (const auto &i : filter)
+               if (i.first == tag)
+                       return i.second.c_str();
+
+       return nullptr;
+}
+
+void
+AddConstraints(struct mpd_connection *connection,
+              const TagFilter &filter) noexcept
+{
+       for (const auto &i : filter)
+               mpd_search_add_tag_constraint(connection,
+                                             MPD_OPERATOR_DEFAULT,
+                                             i.first, i.second.c_str());
+}
diff --git a/src/TagFilter.hxx b/src/TagFilter.hxx
new file mode 100644 (file)
index 0000000..a5aee08
--- /dev/null
@@ -0,0 +1,42 @@
+/* ncmpc (Ncurses MPD Client)
+ * (c) 2004-2018 The Music Player Daemon Project
+ * Project homepage: http://musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef NCMPC_TAG_FILTER_HXX
+#define NCMPC_TAG_FILTER_HXX
+
+#include "util/Compiler.h"
+
+#include <mpd/tag.h>
+
+#include <string>
+#include <forward_list>
+
+class ScreenManager;
+
+using TagFilter = std::forward_list<std::pair<enum mpd_tag_type, std::string>>;
+
+gcc_pure
+const char *
+FindTag(const TagFilter &filter, enum mpd_tag_type tag) noexcept;
+
+void
+AddConstraints(struct mpd_connection *connection,
+              const TagFilter &filter) noexcept;
+
+#endif
similarity index 62%
rename from src/AlbumListPage.cxx
rename to src/TagListPage.cxx
index 4c7b12c..e197bae 100644 (file)
@@ -17,7 +17,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#include "AlbumListPage.hxx"
+#include "TagListPage.hxx"
 #include "screen_status.hxx"
 #include "screen_find.hxx"
 #include "FileListPage.hxx"
 #include <assert.h>
 #include <string.h>
 
+TagFilter
+TagListPage::MakeCursorFilter() const noexcept
+{
+       unsigned i = lw.selected;
+       if (parent != nullptr) {
+               if (i == 0)
+                       return {};
+
+               --i;
+       }
+
+       auto new_filter = filter;
+       if (i < values.size())
+               new_filter.emplace_front(tag, values[i]);
+       return new_filter;
+}
+
 gcc_pure
 static bool
 CompareUTF8(const std::string &a, const std::string &b)
@@ -40,19 +57,22 @@ CompareUTF8(const std::string &a, const std::string &b)
 }
 
 const char *
-AlbumListPage::GetListItemText(char *buffer, size_t size,
-                              unsigned idx) const noexcept
+TagListPage::GetListItemText(char *buffer, size_t size,
+                            unsigned idx) const noexcept
 {
-       if (idx == 0)
-               return "..";
-       else if (idx == album_list.size() + 1)
-               return _("All tracks");
+       if (parent != nullptr) {
+               if (idx == 0)
+                       return "..";
 
-       --idx;
+               --idx;
+       }
 
-       assert(idx < album_list.size());
+       if (idx == values.size() + 1)
+               return _("All tracks");
 
-       return utf8_to_locale(album_list[idx].c_str(), buffer, size);
+       assert(idx < values.size());
+
+       return utf8_to_locale(values[idx].c_str(), buffer, size);
 }
 
 static void
@@ -68,34 +88,31 @@ recv_tag_values(struct mpd_connection *connection, enum mpd_tag_type tag,
 }
 
 void
-AlbumListPage::LoadAlbumList(struct mpdclient &c)
+TagListPage::LoadValues(struct mpdclient &c) noexcept
 {
        auto *connection = c.GetConnection();
 
-       album_list.clear();
+       values.clear();
 
        if (connection != nullptr) {
-               mpd_search_db_tags(connection, MPD_TAG_ALBUM);
-               mpd_search_add_tag_constraint(connection,
-                                             MPD_OPERATOR_DEFAULT,
-                                             MPD_TAG_ARTIST,
-                                             artist.c_str());
+               mpd_search_db_tags(connection, tag);
+               AddConstraints(connection, filter);
                mpd_search_commit(connection);
 
-               recv_tag_values(connection, MPD_TAG_ALBUM, album_list);
+               recv_tag_values(connection, tag, values);
 
                c.FinishCommand();
        }
 
        /* sort list */
-       std::sort(album_list.begin(), album_list.end(), CompareUTF8);
-       lw.SetLength(album_list.size() + 2);
+       std::sort(values.begin(), values.end(), CompareUTF8);
+       lw.SetLength((parent != nullptr) + values.size() + (all_text != nullptr));
 }
 
 void
-AlbumListPage::Reload(struct mpdclient &c)
+TagListPage::Reload(struct mpdclient &c)
 {
-       LoadAlbumList(c);
+       LoadValues(c);
 }
 
 /**
@@ -105,39 +122,42 @@ AlbumListPage::Reload(struct mpdclient &c)
  * to view the tracks of all albums.
  */
 void
-AlbumListPage::PaintListItem(WINDOW *w, unsigned i,
+TagListPage::PaintListItem(WINDOW *w, unsigned i,
                             gcc_unused unsigned y, unsigned width,
                             bool selected) const noexcept
 {
-       if (i == 0)
-               screen_browser_paint_directory(w, width, selected, "..");
-       else if (i == album_list.size() + 1)
+       if (parent != nullptr) {
+               if (i == 0) {
+                       screen_browser_paint_directory(w, width, selected,
+                                                      "..");
+                       return;
+               }
+
+               --i;
+       }
+
+       if (i < values.size())
                screen_browser_paint_directory(w, width, selected,
-                                              _("All tracks"));
+                                              Utf8ToLocale(values[i].c_str()).c_str());
        else
                screen_browser_paint_directory(w, width, selected,
-                                              Utf8ToLocale(album_list[i - 1].c_str()).c_str());
+                                              all_text);
 }
 
 void
-AlbumListPage::Paint() const noexcept
+TagListPage::Paint() const noexcept
 {
        lw.Paint(*this);
 }
 
 const char *
-AlbumListPage::GetTitle(char *str, size_t size) const noexcept
+TagListPage::GetTitle(char *, size_t) const noexcept
 {
-       if (artist.empty())
-               return _("Albums");
-
-       snprintf(str, size, _("Albums of artist: %s"),
-                Utf8ToLocale(artist.c_str()).c_str());
-       return str;
+       return title.c_str();
 }
 
 void
-AlbumListPage::Update(struct mpdclient &c, unsigned events) noexcept
+TagListPage::Update(struct mpdclient &c, unsigned events) noexcept
 {
        if (events & MPD_IDLE_DATABASE) {
                /* the db has changed -> update the list */
@@ -150,34 +170,35 @@ AlbumListPage::Update(struct mpdclient &c, unsigned events) noexcept
    _artist is actually only used in the ALBUM case to distinguish albums with
    the same name from different artists. */
 static void
-add_query(struct mpdclient *c, enum mpd_tag_type table, const char *_filter,
-         const char *_artist)
+add_query(struct mpdclient *c, const TagFilter &filter,
+         enum mpd_tag_type tag, const char *value) noexcept
 {
-       assert(_filter != nullptr);
-
        auto *connection = c->GetConnection();
        if (connection == nullptr)
                return;
 
+       const char *text = value;
+       if (value == nullptr)
+               value = filter.empty() ? "?" : filter.front().second.c_str();
+
        screen_status_printf(_("Adding \'%s\' to queue"),
-                            Utf8ToLocale(_filter).c_str());
+                            Utf8ToLocale(text).c_str());
 
        mpd_search_add_db_songs(connection, true);
-       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                     table, _filter);
-       if (table == MPD_TAG_ALBUM)
+       AddConstraints(connection, filter);
+
+       if (value != nullptr)
                mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                             MPD_TAG_ARTIST, _artist);
+                                             tag, value);
+
        mpd_search_commit(connection);
        c->FinishCommand();
 }
 
 bool
-AlbumListPage::OnCommand(struct mpdclient &c, Command cmd)
+TagListPage::OnCommand(struct mpdclient &c, Command cmd)
 {
        switch(cmd) {
-               const char *selected;
-
        case Command::PLAY:
                if (lw.selected == 0 && parent != nullptr)
                        /* handle ".." */
@@ -187,16 +208,18 @@ AlbumListPage::OnCommand(struct mpdclient &c, Command cmd)
 
        case Command::SELECT:
        case Command::ADD:
-               for (const unsigned i : lw.GetRange()) {
-                       if(i == album_list.size() + 1)
-                               add_query(&c, MPD_TAG_ARTIST, artist.c_str(),
-                                         nullptr);
-                       else if (i > 0) {
-                               selected = album_list[lw.selected - 1].c_str();
-                               add_query(&c, MPD_TAG_ALBUM, selected,
-                                         artist.c_str());
-                               cmd = Command::LIST_NEXT; /* continue and select next item... */
+               for (unsigned i : lw.GetRange()) {
+                       if (parent != nullptr) {
+                               if (i == 0)
+                                       continue;
+
+                               --i;
                        }
+
+                       add_query(&c, filter, tag,
+                                 i < values.size()
+                                 ? values[i].c_str() : nullptr);
+                       cmd = Command::LIST_NEXT; /* continue and select next item... */
                }
                break;
 
similarity index 62%
rename from src/AlbumListPage.hxx
rename to src/TagListPage.hxx
index d1d153a..01f7f5b 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-#ifndef NCMPC_ALBUM_LIST_PAGE_HXX
-#define NCMPC_ALBUM_LIST_PAGE_HXX
+#ifndef NCMPC_TAG_LIST_PAGE_HXX
+#define NCMPC_TAG_LIST_PAGE_HXX
 
+#include "TagFilter.hxx"
 #include "ListPage.hxx"
 #include "ListRenderer.hxx"
 #include "ListText.hxx"
 
 class ScreenManager;
 
-class AlbumListPage final : public ListPage, ListRenderer, ListText {
+class TagListPage final : public ListPage, ListRenderer, ListText {
        ScreenManager &screen;
        Page *const parent;
-       std::vector<std::string> album_list;
-       std::string artist;
+
+       const enum mpd_tag_type tag;
+       const char *const all_text;
+
+       TagFilter filter;
+       std::string title;
+
+       std::vector<std::string> values;
 
 public:
-       AlbumListPage(ScreenManager &_screen, Page *_parent,
-                     WINDOW *_w, Size size) noexcept
-               :ListPage(_w, size), screen(_screen), parent(_parent) {}
+       TagListPage(ScreenManager &_screen, Page *_parent,
+                   const enum mpd_tag_type _tag,
+                   const char *_all_text,
+                   WINDOW *_w, Size size) noexcept
+               :ListPage(_w, size), screen(_screen), parent(_parent),
+                tag(_tag), all_text(_all_text) {}
 
-       template<typename A>
-       void SetArtist(A &&_artist) {
-               artist = std::forward<A>(_artist);
-               AddPendingEvents(~0u);
+       const auto &GetFilter() const noexcept {
+               return filter;
        }
 
-       const std::string &GetArtist() const {
-               return artist;
+       template<typename F, typename T>
+       void SetFilter(F &&_filter, T &&_title) noexcept {
+               filter = std::forward<F>(_filter);
+               title = std::forward<T>(_title);
+               AddPendingEvents(~0u);
        }
 
-       bool IsShowAll() const {
-               return lw.selected == album_list.size() + 1;
-       }
+       /**
+        * Create a filter for the item below the cursor.
+        */
+       TagFilter MakeCursorFilter() const noexcept;
 
+       gcc_pure
        const char *GetSelectedValue() const {
-               return lw.selected >= 1 && lw.selected <= album_list.size()
-                       ? album_list[lw.selected - 1].c_str()
+               unsigned i = lw.selected;
+
+               if (parent != nullptr) {
+                       if (i == 0)
+                               return nullptr;
+
+                       --i;
+               }
+
+               return i < values.size()
+                       ? values[i].c_str()
                        : nullptr;
        }
 
 private:
-       void LoadAlbumList(struct mpdclient &c);
+       void LoadValues(struct mpdclient &c) noexcept;
        void Reload(struct mpdclient &c);
 
 public:
index 0b80af5..db43c38 100644 (file)
@@ -18,8 +18,7 @@
  */
 
 #include "screen_artist.hxx"
-#include "ArtistListPage.hxx"
-#include "AlbumListPage.hxx"
+#include "TagListPage.hxx"
 #include "PageMeta.hxx"
 #include "screen_status.hxx"
 #include "screen_find.hxx"
 class SongListPage final : public FileListPage {
        Page *const parent;
 
-       std::string artist;
-
-       /**
-        * The current album filter.  If IsNulled() is true, then the
-        * album filter is not used (i.e. all songs from all albums
-        * are displayed).
-        */
-       std::string album;
+       TagFilter filter;
 
 public:
        SongListPage(ScreenManager &_screen, Page *_parent,
@@ -60,26 +52,16 @@ public:
                              options.list_format.c_str()),
                 parent(_parent) {}
 
-       template<typename A>
-       void SetArtist(A &&_artist) {
-               artist = std::forward<A>(_artist);
-               AddPendingEvents(~0u);
-       }
-
-       const std::string &GetArtist() {
-               return artist;
+       const auto &GetFilter() const noexcept {
+               return filter;
        }
 
-       template<typename A>
-       void SetAlbum(A &&_album) {
-               album = std::forward<A>(_album);
+       template<typename F>
+       void SetFilter(F &&_filter) noexcept {
+               filter = std::forward<F>(_filter);
                AddPendingEvents(~0u);
        }
 
-       const std::string &GetAlbum() {
-               return album;
-       }
-
        void LoadSongList(struct mpdclient &c);
 
        /* virtual methods from class Page */
@@ -97,23 +79,28 @@ SongListPage::Update(struct mpdclient &c, unsigned events) noexcept
 }
 
 class ArtistBrowserPage final : public ProxyPage {
-       ArtistListPage artist_list_page;
-       AlbumListPage album_list_page;
+       TagListPage artist_list_page;
+       TagListPage album_list_page;
        SongListPage song_list_page;
 
 public:
        ArtistBrowserPage(ScreenManager &_screen, WINDOW *_w,
                          Size size)
                :ProxyPage(_w),
-                artist_list_page(_screen, _w, size),
-                album_list_page(_screen, this, _w, size),
+                artist_list_page(_screen, nullptr,
+                                 MPD_TAG_ARTIST,
+                                 nullptr,
+                                 _w, size),
+                album_list_page(_screen, this,
+                                MPD_TAG_ALBUM,
+                                _("All tracks"),
+                                _w, size),
                 song_list_page(_screen, this, _w, size) {}
 
 private:
        void OpenArtistList(struct mpdclient &c);
        void OpenAlbumList(struct mpdclient &c, std::string _artist);
-       void OpenSongList(struct mpdclient &c, std::string _artist,
-                         std::string _album);
+       void OpenSongList(struct mpdclient &c, TagFilter &&filter);
 
 public:
        /* virtual methods from class Page */
@@ -135,11 +122,7 @@ SongListPage::LoadSongList(struct mpdclient &c)
 
        if (connection != nullptr) {
                mpd_search_db_songs(connection, true);
-               mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                             MPD_TAG_ARTIST, artist.c_str());
-               if (!IsNulled(album))
-                       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                                     MPD_TAG_ALBUM, album.c_str());
+               AddConstraints(connection, filter);
                mpd_search_commit(connection);
 
                filelist->Receive(*connection);
@@ -155,22 +138,35 @@ SongListPage::LoadSongList(struct mpdclient &c)
 void
 ArtistBrowserPage::OpenArtistList(struct mpdclient &c)
 {
+       artist_list_page.SetFilter(TagFilter{}, _("All artists"));
+
        SetCurrentPage(c, &artist_list_page);
 }
 
 void
 ArtistBrowserPage::OpenAlbumList(struct mpdclient &c, std::string _artist)
 {
-       album_list_page.SetArtist(std::move(_artist));
+       char buffer[64];
+       const char *title;
+
+       if (_artist.empty()) {
+               title = _("Albums");
+       } else {
+               snprintf(buffer, sizeof(buffer), _("Albums of artist: %s"),
+                        Utf8ToLocale(_artist.c_str()).c_str());
+               title = buffer;
+       }
+
+       album_list_page.SetFilter(TagFilter{{MPD_TAG_ARTIST, std::move(_artist)}},
+                                 title);
+
        SetCurrentPage(c, &album_list_page);
 }
 
 void
-ArtistBrowserPage::OpenSongList(struct mpdclient &c, std::string _artist,
-                               std::string _album)
+ArtistBrowserPage::OpenSongList(struct mpdclient &c, TagFilter &&filter)
 {
-       song_list_page.SetArtist(std::move(_artist));
-       song_list_page.SetAlbum(std::move(_album));
+       song_list_page.SetFilter(std::move(filter));
        SetCurrentPage(c, &song_list_page);
 }
 
@@ -183,21 +179,25 @@ screen_artist_init(ScreenManager &_screen, WINDOW *w, Size size)
 const char *
 SongListPage::GetTitle(char *str, size_t size) const noexcept
 {
-       const Utf8ToLocale artist_locale(artist.c_str());
+       const char *artist = FindTag(filter, MPD_TAG_ARTIST);
+       if (artist == nullptr)
+               artist = "?";
+
+       const char *const album = FindTag(filter, MPD_TAG_ALBUM);
 
-       if (IsNulled(album))
+       if (album == nullptr)
                snprintf(str, size,
                         _("All tracks of artist: %s"),
-                        artist_locale.c_str());
-       else if (!album.empty()) {
-               const Utf8ToLocale album_locale(album.c_str());
+                        Utf8ToLocale(artist).c_str());
+       else if (*album != '\0')
                snprintf(str, size, "%s: %s - %s",
                         _("Album"),
-                        artist_locale.c_str(), album_locale.c_str());
-       } else
+                        Utf8ToLocale(artist).c_str(),
+                        Utf8ToLocale(album).c_str());
+       else
                snprintf(str, size,
                         _("Tracks of no album of artist: %s"),
-                        artist_locale.c_str());
+                        Utf8ToLocale(artist).c_str());
 
        return str;
 }
@@ -259,13 +259,9 @@ ArtistBrowserPage::OnCommand(struct mpdclient &c, Command cmd)
                                return true;
                        }
                } else if (GetCurrentPage() == &album_list_page) {
-                       const char *album = album_list_page.GetSelectedValue();
-                       if (album != nullptr)
-                               OpenSongList(c, album_list_page.GetArtist(),
-                                            album);
-                       else if (album_list_page.IsShowAll())
-                               OpenSongList(c, album_list_page.GetArtist(),
-                                            MakeNulledString());
+                       auto filter = album_list_page.MakeCursorFilter();
+                       if (!filter.empty())
+                               OpenSongList(c, std::move(filter));
                }
 
                break;
@@ -283,7 +279,7 @@ ArtistBrowserPage::OnCommand(struct mpdclient &c, Command cmd)
                        OpenArtistList(c);
                        return true;
                } else if (GetCurrentPage() == &song_list_page) {
-                       OpenAlbumList(c, song_list_page.GetArtist());
+                       OpenAlbumList(c, FindTag(song_list_page.GetFilter(), MPD_TAG_ARTIST));
                        return true;
                }