screen_artist: split into three Page implementations
authorMax Kellermann <max@musicpd.org>
Mon, 26 Feb 2018 22:02:25 +0000 (23:02 +0100)
committerMax Kellermann <max@musicpd.org>
Wed, 14 Mar 2018 19:50:55 +0000 (20:50 +0100)
meson.build
src/AlbumListPage.cxx [new file with mode: 0644]
src/AlbumListPage.hxx [new file with mode: 0644]
src/ArtistListPage.cxx [new file with mode: 0644]
src/ArtistListPage.hxx [new file with mode: 0644]
src/screen_artist.cxx

index 6f569eb..c1562bc 100644 (file)
@@ -254,7 +254,11 @@ endif
 enable_artist_screen = get_option('artist_screen') and not mini
 conf.set('ENABLE_ARTIST_SCREEN', enable_artist_screen)
 if enable_artist_screen
 enable_artist_screen = get_option('artist_screen') and not mini
 conf.set('ENABLE_ARTIST_SCREEN', enable_artist_screen)
 if enable_artist_screen
-  sources += ['src/screen_artist.cxx']
+  sources += [
+    'src/screen_artist.cxx',
+    'src/ArtistListPage.cxx',
+    'src/AlbumListPage.cxx',
+  ]
 endif
 
 enable_search_screen = get_option('search_screen') and not mini
 endif
 
 enable_search_screen = get_option('search_screen') and not mini
diff --git a/src/AlbumListPage.cxx b/src/AlbumListPage.cxx
new file mode 100644 (file)
index 0000000..4120801
--- /dev/null
@@ -0,0 +1,281 @@
+/* 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 "AlbumListPage.hxx"
+#include "screen_interface.hxx"
+#include "screen_status.hxx"
+#include "screen_find.hxx"
+#include "screen_browser.hxx"
+#include "screen.hxx"
+#include "i18n.h"
+#include "charset.hxx"
+#include "mpdclient.hxx"
+#include "filelist.hxx"
+
+#include <glib.h>
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+
+#define BUFSIZE 1024
+
+gcc_pure
+static bool
+CompareUTF8(const std::string &a, const std::string &b)
+{
+       char *key1 = g_utf8_collate_key(a.c_str(), -1);
+       char *key2 = g_utf8_collate_key(b.c_str(), -1);
+       int n = strcmp(key1,key2);
+       g_free(key1);
+       g_free(key2);
+       return n < 0;
+}
+
+/* list_window callback */
+static const char *
+album_lw_callback(unsigned idx, void *data)
+{
+       const auto &list = *(const std::vector<std::string> *)data;
+
+       assert(idx < list.size());
+
+       const char *str_utf8 = list[idx].c_str();
+       char *str = utf8_to_locale(str_utf8);
+
+       static char buf[BUFSIZE];
+       g_strlcpy(buf, str, sizeof(buf));
+       g_free(str);
+
+       return buf;
+}
+
+/* list_window callback */
+static const char *
+AlbumListCallback(unsigned idx, void *data)
+{
+       const auto &list = *(const std::vector<std::string> *)data;
+
+       if (idx == 0)
+               return "..";
+       else if (idx == list.size() + 1)
+               return _("All tracks");
+
+       --idx;
+
+       return album_lw_callback(idx, data);
+}
+
+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
+AlbumListPage::LoadAlbumList(struct mpdclient &c)
+{
+       struct mpd_connection *connection = mpdclient_get_connection(&c);
+
+       album_list.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_commit(connection);
+
+               recv_tag_values(connection, MPD_TAG_ALBUM, album_list);
+
+               mpdclient_finish_command(&c);
+       }
+
+       /* sort list */
+       std::sort(album_list.begin(), album_list.end(), CompareUTF8);
+       lw.SetLength(album_list.size() + 2);
+}
+
+void
+AlbumListPage::Reload(struct mpdclient &c)
+{
+       LoadAlbumList(c);
+}
+
+/**
+ * Paint one item in the album list.  There are two virtual items
+ * inserted: at the beginning, there's the special item ".." to go to
+ * the parent directory, and at the end, there's the item "All tracks"
+ * to view the tracks of all albums.
+ */
+static void
+paint_album_callback(WINDOW *w, unsigned i,
+                    gcc_unused unsigned y, unsigned width,
+                    bool selected, const void *data)
+{
+       const auto &list = *(const std::vector<std::string> *)data;
+       const char *p;
+       char *q = nullptr;
+
+       if (i == 0)
+               p = "..";
+       else if (i == list.size() + 1)
+               p = _("All tracks");
+       else
+               p = q = utf8_to_locale(list[i - 1].c_str());
+
+       screen_browser_paint_directory(w, width, selected, p);
+       g_free(q);
+}
+
+void
+AlbumListPage::Paint() const
+{
+       lw.Paint(paint_album_callback, &album_list);
+}
+
+const char *
+AlbumListPage::GetTitle(char *str, size_t size) const
+{
+       if (artist.empty())
+               return _("Albums");
+
+       char *s1 = utf8_to_locale(artist.c_str());
+       g_snprintf(str, size, _("Albums of artist: %s"), s1);
+       g_free(s1);
+       return str;
+}
+
+void
+AlbumListPage::Update(struct mpdclient &c, unsigned events)
+{
+       if (events & MPD_IDLE_DATABASE) {
+               /* the db has changed -> update the list */
+               Reload(c);
+               SetDirty();
+       }
+}
+
+/* add_query - Add all songs satisfying specified criteria.
+   _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)
+{
+       struct mpd_connection *connection = mpdclient_get_connection(c);
+
+       assert(_filter != nullptr);
+
+       if (connection == nullptr)
+               return;
+
+       char *str = utf8_to_locale(_filter);
+       if (table == MPD_TAG_ALBUM)
+               screen_status_printf(_("Adding album %s..."), str);
+       else
+               screen_status_printf(_("Adding %s..."), str);
+       g_free(str);
+
+       mpd_search_db_songs(connection, true);
+       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
+                                     table, _filter);
+       if (table == MPD_TAG_ALBUM)
+               mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
+                                             MPD_TAG_ARTIST, _artist);
+       mpd_search_commit(connection);
+
+       auto *addlist = filelist_new_recv(connection);
+
+       if (mpdclient_finish_command(c))
+               mpdclient_filelist_add_all(c, addlist);
+
+       delete addlist;
+}
+
+bool
+AlbumListPage::OnCommand(struct mpdclient &c, command_t cmd)
+{
+       switch(cmd) {
+               const char *selected;
+
+       case CMD_PLAY:
+               if (lw.selected == 0) {
+                       /* handle ".." */
+                       screen.OnCommand(c, CMD_GO_PARENT_DIRECTORY);
+                       return true;
+               }
+
+               break;
+
+       case CMD_SELECT:
+       case CMD_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 = CMD_LIST_NEXT; /* continue and select next item... */
+                       }
+               }
+               break;
+
+               /* continue and update... */
+       case CMD_SCREEN_UPDATE:
+               Reload(c);
+               return false;
+
+       case CMD_LIST_FIND:
+       case CMD_LIST_RFIND:
+       case CMD_LIST_FIND_NEXT:
+       case CMD_LIST_RFIND_NEXT:
+               screen_find(screen, &lw, cmd,
+                           AlbumListCallback, &album_list);
+               SetDirty();
+               return true;
+
+       case CMD_LIST_JUMP:
+               screen_jump(screen, &lw,
+                           AlbumListCallback, &album_list,
+                           paint_album_callback, &album_list);
+               SetDirty();
+               return true;
+
+       default:
+               break;
+       }
+
+       if (lw.HandleCommand(cmd)) {
+               SetDirty();
+               return true;
+       }
+
+       return false;
+}
diff --git a/src/AlbumListPage.hxx b/src/AlbumListPage.hxx
new file mode 100644 (file)
index 0000000..7871c78
--- /dev/null
@@ -0,0 +1,71 @@
+/* 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_ALBUM_LIST_PAGE_HXX
+#define NCMPC_ALBUM_LIST_PAGE_HXX
+
+#include "ListPage.hxx"
+
+#include <vector>
+#include <string>
+
+class ScreenManager;
+
+class AlbumListPage final : public ListPage {
+       ScreenManager &screen;
+       std::vector<std::string> album_list;
+       std::string artist;
+
+public:
+       AlbumListPage(ScreenManager &_screen, WINDOW *_w, Size size)
+               :ListPage(_w, size), screen(_screen) {}
+
+       template<typename A>
+       void SetArtist(A &&_artist) {
+               artist = std::forward<A>(_artist);
+               AddPendingEvents(~0u);
+       }
+
+       const std::string &GetArtist() const {
+               return artist;
+       }
+
+       bool IsShowAll() const {
+               return lw.selected == album_list.size() + 1;
+       }
+
+       const char *GetSelectedValue() const {
+               return lw.selected >= 1 && lw.selected <= album_list.size()
+                       ? album_list[lw.selected - 1].c_str()
+                       : nullptr;
+       }
+
+private:
+       void LoadAlbumList(struct mpdclient &c);
+       void Reload(struct mpdclient &c);
+
+public:
+       /* virtual methods from class Page */
+       void Paint() const override;
+       void Update(struct mpdclient &c, unsigned events) override;
+       bool OnCommand(struct mpdclient &c, command_t cmd) override;
+       const char *GetTitle(char *s, size_t size) const override;
+};
+
+#endif
diff --git a/src/ArtistListPage.cxx b/src/ArtistListPage.cxx
new file mode 100644 (file)
index 0000000..bf6dec8
--- /dev/null
@@ -0,0 +1,240 @@
+/* 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_interface.hxx"
+#include "screen_status.hxx"
+#include "screen_find.hxx"
+#include "screen_browser.hxx"
+#include "i18n.h"
+#include "charset.hxx"
+#include "mpdclient.hxx"
+#include "filelist.hxx"
+
+#include <algorithm>
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#define BUFSIZE 1024
+
+gcc_pure
+static bool
+CompareUTF8(const std::string &a, const std::string &b)
+{
+       char *key1 = g_utf8_collate_key(a.c_str(), -1);
+       char *key2 = g_utf8_collate_key(b.c_str(), -1);
+       int n = strcmp(key1,key2);
+       g_free(key1);
+       g_free(key2);
+       return n < 0;
+}
+
+/* list_window callback */
+static const char *
+screen_artist_lw_callback(unsigned idx, void *data)
+{
+       const auto &list = *(const std::vector<std::string> *)data;
+
+       assert(idx < list.size());
+
+       const char *str_utf8 = list[idx].c_str();
+       char *str = utf8_to_locale(str_utf8);
+
+       static char buf[BUFSIZE];
+       g_strlcpy(buf, str, sizeof(buf));
+       g_free(str);
+
+       return buf;
+}
+
+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)
+{
+       struct mpd_connection *connection = mpdclient_get_connection(&c);
+
+       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);
+
+               mpdclient_finish_command(&c);
+       }
+
+       /* sort list */
+       std::sort(artist_list.begin(), artist_list.end(), CompareUTF8);
+       lw.SetLength(artist_list.size());
+}
+
+void
+ArtistListPage::Reload(struct mpdclient &c)
+{
+       LoadArtistList(c);
+}
+
+/**
+ * Paint one item in the artist list.
+ */
+static void
+paint_artist_callback(WINDOW *w, unsigned i,
+                     gcc_unused unsigned y, unsigned width,
+                     bool selected, const void *data)
+{
+       const auto &list = *(const std::vector<std::string> *)data;
+       char *p = utf8_to_locale(list[i].c_str());
+
+       screen_browser_paint_directory(w, width, selected, p);
+       g_free(p);
+}
+
+void
+ArtistListPage::Paint() const
+{
+       lw.Paint(paint_artist_callback, &artist_list);
+}
+
+const char *
+ArtistListPage::GetTitle(char *, size_t) const
+{
+       return _("All artists");
+}
+
+void
+ArtistListPage::Update(struct mpdclient &c, unsigned events)
+{
+       if (events & MPD_IDLE_DATABASE) {
+               /* the db has changed -> update the list */
+               Reload(c);
+               SetDirty();
+       }
+}
+
+/* add_query - Add all songs satisfying specified criteria.
+   _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)
+{
+       struct mpd_connection *connection = mpdclient_get_connection(c);
+
+       assert(_filter != nullptr);
+
+       if (connection == nullptr)
+               return;
+
+       char *str = utf8_to_locale(_filter);
+       if (table == MPD_TAG_ALBUM)
+               screen_status_printf(_("Adding album %s..."), str);
+       else
+               screen_status_printf(_("Adding %s..."), str);
+       g_free(str);
+
+       mpd_search_db_songs(connection, true);
+       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
+                                     table, _filter);
+       if (table == MPD_TAG_ALBUM)
+               mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
+                                             MPD_TAG_ARTIST, _artist);
+       mpd_search_commit(connection);
+
+       auto *addlist = filelist_new_recv(connection);
+
+       if (mpdclient_finish_command(c))
+               mpdclient_filelist_add_all(c, addlist);
+
+       delete addlist;
+}
+
+inline bool
+ArtistListPage::OnListCommand(command_t cmd)
+{
+       if (lw.HandleCommand(cmd)) {
+               SetDirty();
+               return true;
+       }
+
+       return false;
+}
+
+bool
+ArtistListPage::OnCommand(struct mpdclient &c, command_t cmd)
+{
+       switch(cmd) {
+               const char *selected;
+
+       case CMD_SELECT:
+       case CMD_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, nullptr);
+                       cmd = CMD_LIST_NEXT; /* continue and select next item... */
+               }
+
+               break;
+
+               /* continue and update... */
+       case CMD_SCREEN_UPDATE:
+               Reload(c);
+               return false;
+
+       case CMD_LIST_FIND:
+       case CMD_LIST_RFIND:
+       case CMD_LIST_FIND_NEXT:
+       case CMD_LIST_RFIND_NEXT:
+               screen_find(screen, &lw, cmd,
+                           screen_artist_lw_callback, &artist_list);
+               SetDirty();
+               return true;
+
+       case CMD_LIST_JUMP:
+               screen_jump(screen, &lw,
+                           screen_artist_lw_callback, &artist_list,
+                           paint_artist_callback, &artist_list);
+               SetDirty();
+               return true;
+
+       default:
+               break;
+       }
+
+       if (OnListCommand(cmd))
+               return true;
+
+       return false;
+}
diff --git a/src/ArtistListPage.hxx b/src/ArtistListPage.hxx
new file mode 100644 (file)
index 0000000..075177c
--- /dev/null
@@ -0,0 +1,58 @@
+/* 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 <vector>
+#include <string>
+
+class ScreenManager;
+
+class ArtistListPage final : public ListPage {
+       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_t cmd);
+
+public:
+       /* virtual methods from class Page */
+       void Paint() const override;
+       void Update(struct mpdclient &c, unsigned events) override;
+       bool OnCommand(struct mpdclient &c, command_t cmd) override;
+       const char *GetTitle(char *s, size_t size) const override;
+};
+
+#endif
index 30de3df..77a3927 100644 (file)
  */
 
 #include "screen_artist.hxx"
  */
 
 #include "screen_artist.hxx"
+#include "ArtistListPage.hxx"
+#include "AlbumListPage.hxx"
 #include "screen_interface.hxx"
 #include "screen_status.hxx"
 #include "screen_find.hxx"
 #include "screen_browser.hxx"
 #include "screen_interface.hxx"
 #include "screen_status.hxx"
 #include "screen_find.hxx"
 #include "screen_browser.hxx"
+#include "screen.hxx"
+#include "ProxyPage.hxx"
 #include "i18n.h"
 #include "charset.hxx"
 #include "mpdclient.hxx"
 #include "i18n.h"
 #include "charset.hxx"
 #include "mpdclient.hxx"
 
 #define BUFSIZE 1024
 
 
 #define BUFSIZE 1024
 
-class ArtistBrowserPage final : public FileListPage {
-       enum class Mode {
-               ARTISTS,
-               ALBUMS,
-               SONGS,
-       } mode = Mode::ARTISTS;
-
-       std::vector<std::string> artist_list, album_list;
+class SongListPage final : public FileListPage {
        std::string artist;
 
        /**
        std::string artist;
 
        /**
@@ -57,185 +54,86 @@ class ArtistBrowserPage final : public FileListPage {
        std::string album;
 
 public:
        std::string album;
 
 public:
-       ArtistBrowserPage(ScreenManager &_screen, WINDOW *_w,
-                         Size size)
-               :FileListPage(_screen, _w, size,
-                             options.list_format) {}
+       SongListPage(ScreenManager &_screen, WINDOW *_w, Size size)
+               :FileListPage(_screen, _w, size, options.list_format) {}
 
 
-private:
-       void FreeLists();
-       void LoadArtistList(struct mpdclient &c);
-       void LoadAlbumList(struct mpdclient &c);
-       void LoadSongList(struct mpdclient &c);
-       void Reload(struct mpdclient &c);
+       template<typename A>
+       void SetArtist(A &&_artist) {
+               artist = std::forward<A>(_artist);
+               AddPendingEvents(~0u);
+       }
 
 
-       void OpenArtistList(struct mpdclient &c);
-       void OpenArtistList(struct mpdclient &c, const char *selected_value);
-       void OpenAlbumList(struct mpdclient &c, std::string _artist);
-       void OpenAlbumList(struct mpdclient &c, std::string _artist,
-                          const char *selected_value);
-       void OpenSongList(struct mpdclient &c, std::string _artist,
-                         std::string _album);
+       const std::string &GetArtist() {
+               return artist;
+       }
 
 
-       bool OnListCommand(struct mpdclient &c, command_t cmd);
+       template<typename A>
+       void SetAlbum(A &&_album) {
+               album = std::forward<A>(_album);
+               AddPendingEvents(~0u);
+       }
+
+       const std::string &GetAlbum() {
+               return album;
+       }
+
+       void LoadSongList(struct mpdclient &c);
 
 
-public:
        /* virtual methods from class Page */
        /* virtual methods from class Page */
-       void OnOpen(struct mpdclient &c) override;
-       void Paint() const override;
        void Update(struct mpdclient &c, unsigned events) override;
        bool OnCommand(struct mpdclient &c, command_t cmd) override;
        const char *GetTitle(char *s, size_t size) const override;
 };
 
        void Update(struct mpdclient &c, unsigned events) override;
        bool OnCommand(struct mpdclient &c, command_t cmd) override;
        const char *GetTitle(char *s, size_t size) const override;
 };
 
-gcc_pure
-static int
-string_array_find(const std::vector<std::string> &array, const char *value)
-{
-       for (size_t i = 0; i < array.size(); ++i)
-               if (array[i] == value)
-                       return i;
-
-       return -1;
-}
-
-gcc_pure
-static bool
-CompareUTF8(const std::string &a, const std::string &b)
-{
-       char *key1 = g_utf8_collate_key(a.c_str(), -1);
-       char *key2 = g_utf8_collate_key(b.c_str(), -1);
-       int n = strcmp(key1,key2);
-       g_free(key1);
-       g_free(key2);
-       return n < 0;
-}
-
-/* list_window callback */
-static const char *
-screen_artist_lw_callback(unsigned idx, void *data)
-{
-       const auto &list = *(const std::vector<std::string> *)data;
-
-       /*
-       if (mode == Mode::ALBUMS) {
-               if (idx == 0)
-                       return "..";
-               else if (idx == list.size() + 1)
-                       return _("All tracks");
-
-               --idx;
-       }
-       */
-
-       assert(idx < list.size());
-
-       const char *str_utf8 = list[idx].c_str();
-       char *str = utf8_to_locale(str_utf8);
-
-       static char buf[BUFSIZE];
-       g_strlcpy(buf, str, sizeof(buf));
-       g_free(str);
-
-       return buf;
-}
-
-/* list_window callback */
-static const char *
-AlbumListCallback(unsigned idx, void *data)
-{
-       const auto &list = *(const std::vector<std::string> *)data;
-
-       if (idx == 0)
-               return "..";
-       else if (idx == list.size() + 1)
-               return _("All tracks");
-
-       --idx;
-
-       return screen_artist_lw_callback(idx, data);
-}
-
-void
-ArtistBrowserPage::FreeLists()
-{
-       artist_list.clear();
-       album_list.clear();
-
-       delete filelist;
-       filelist = nullptr;
-}
-
-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
 void
-ArtistBrowserPage::LoadArtistList(struct mpdclient &c)
+SongListPage::Update(struct mpdclient &c, unsigned events)
 {
 {
-       struct mpd_connection *connection = mpdclient_get_connection(&c);
-
-       assert(mode == Mode::ARTISTS);
-       assert(artist_list.empty());
-       assert(album_list.empty());
-       assert(filelist == nullptr);
-
-       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);
-
-               mpdclient_finish_command(&c);
+       if (events & MPD_IDLE_DATABASE) {
+               LoadSongList(c);
        }
        }
-
-       /* sort list */
-       std::sort(artist_list.begin(), artist_list.end(), CompareUTF8);
-       lw.SetLength(artist_list.size());
 }
 
 }
 
-void
-ArtistBrowserPage::LoadAlbumList(struct mpdclient &c)
-{
-       struct mpd_connection *connection = mpdclient_get_connection(&c);
-
-       assert(mode == Mode::ALBUMS);
-       assert(album_list.empty());
-       assert(filelist == nullptr);
+class ArtistBrowserPage final : public ProxyPage {
+       enum class Mode {
+               ARTISTS,
+               ALBUMS,
+               SONGS,
+       } mode = Mode::ARTISTS;
 
 
-       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_commit(connection);
+       ArtistListPage artist_list_page;
+       AlbumListPage album_list_page;
+       SongListPage song_list_page;
 
 
-               recv_tag_values(connection, MPD_TAG_ALBUM, album_list);
+public:
+       ArtistBrowserPage(ScreenManager &_screen, WINDOW *_w,
+                         Size size)
+               :ProxyPage(_w),
+                artist_list_page(_screen, _w, size),
+                album_list_page(_screen, _w, size),
+                song_list_page(_screen, _w, size) {}
 
 
-               mpdclient_finish_command(&c);
-       }
+private:
+       void OpenArtistList(struct mpdclient &c);
+       void OpenArtistList(struct mpdclient &c, const char *selected_value);
+       void OpenAlbumList(struct mpdclient &c, std::string _artist);
+       void OpenAlbumList(struct mpdclient &c, std::string _artist,
+                          const char *selected_value);
+       void OpenSongList(struct mpdclient &c, std::string _artist,
+                         std::string _album);
 
 
-       /* sort list */
-       std::sort(album_list.begin(), album_list.end(), CompareUTF8);
-       lw.SetLength(album_list.size() + 2);
-}
+public:
+       /* virtual methods from class Page */
+       void OnOpen(struct mpdclient &c) override;
+       void Update(struct mpdclient &c, unsigned events) override;
+       bool OnCommand(struct mpdclient &c, command_t cmd) override;
+};
 
 void
 
 void
-ArtistBrowserPage::LoadSongList(struct mpdclient &c)
+SongListPage::LoadSongList(struct mpdclient &c)
 {
        struct mpd_connection *connection = mpdclient_get_connection(&c);
 
 {
        struct mpd_connection *connection = mpdclient_get_connection(&c);
 
-       assert(mode == Mode::SONGS);
-       assert(filelist == nullptr);
+       delete filelist;
 
        filelist = new FileList();
        /* add a dummy entry for ".." */
 
        filelist = new FileList();
        /* add a dummy entry for ".." */
@@ -263,10 +161,8 @@ ArtistBrowserPage::LoadSongList(struct mpdclient &c)
 void
 ArtistBrowserPage::OpenArtistList(struct mpdclient &c)
 {
 void
 ArtistBrowserPage::OpenArtistList(struct mpdclient &c)
 {
-       FreeLists();
-
        mode = Mode::ARTISTS;
        mode = Mode::ARTISTS;
-       LoadArtistList(c);
+       SetCurrentPage(c, &artist_list_page);
 }
 
 void
 }
 
 void
@@ -275,23 +171,16 @@ ArtistBrowserPage::OpenArtistList(struct mpdclient &c,
 {
        OpenArtistList(c);
 
 {
        OpenArtistList(c);
 
-       lw.Reset();
-
-       int idx = string_array_find(artist_list, selected_value);
-       if (idx >= 0) {
-               lw.SetCursor(idx);
-               lw.Center(idx);
-       }
+       // TODO: move cursor to selected_value
+       (void)selected_value;
 }
 
 void
 ArtistBrowserPage::OpenAlbumList(struct mpdclient &c, std::string _artist)
 {
 }
 
 void
 ArtistBrowserPage::OpenAlbumList(struct mpdclient &c, std::string _artist)
 {
-       FreeLists();
-
        mode = Mode::ALBUMS;
        mode = Mode::ALBUMS;
-       artist = std::move(_artist);
-       LoadAlbumList(c);
+       album_list_page.SetArtist(std::move(_artist));
+       SetCurrentPage(c, &album_list_page);
 }
 
 void
 }
 
 void
@@ -300,48 +189,18 @@ ArtistBrowserPage::OpenAlbumList(struct mpdclient &c, std::string _artist,
 {
        OpenAlbumList(c, std::move(_artist));
 
 {
        OpenAlbumList(c, std::move(_artist));
 
-       lw.Reset();
-
-       int idx = selected_value == nullptr
-               ? (int)album_list.size()
-               : string_array_find(album_list, selected_value);
-       if (idx >= 0) {
-               ++idx;
-               lw.SetCursor(idx);
-               lw.Center(idx);
-       }
+       // TODO: move cursor to selected_value
+       (void)selected_value;
 }
 
 void
 }
 
 void
-ArtistBrowserPage::OpenSongList(struct mpdclient &c,
-                               std::string _artist, std::string _album)
+ArtistBrowserPage::OpenSongList(struct mpdclient &c, std::string _artist,
+                               std::string _album)
 {
 {
-       FreeLists();
-
        mode = Mode::SONGS;
        mode = Mode::SONGS;
-       artist = std::move(_artist);
-       album = std::move(_album);
-       LoadSongList(c);
-}
-
-void
-ArtistBrowserPage::Reload(struct mpdclient &c)
-{
-       FreeLists();
-
-       switch (mode) {
-       case Mode::ARTISTS:
-               LoadArtistList(c);
-               break;
-
-       case Mode::ALBUMS:
-               LoadAlbumList(c);
-               break;
-
-       case Mode::SONGS:
-               LoadSongList(c);
-               break;
-       }
+       song_list_page.SetArtist(std::move(_artist));
+       song_list_page.SetAlbum(std::move(_album));
+       SetCurrentPage(c, &song_list_page);
 }
 
 static Page *
 }
 
 static Page *
@@ -350,357 +209,113 @@ screen_artist_init(ScreenManager &_screen, WINDOW *w, Size size)
        return new ArtistBrowserPage(_screen, w, size);
 }
 
        return new ArtistBrowserPage(_screen, w, size);
 }
 
-void
-ArtistBrowserPage::OnOpen(struct mpdclient &c)
-{
-       if (artist_list.empty())
-               Reload(c);
-}
-
-/**
- * Paint one item in the artist list.
- */
-static void
-paint_artist_callback(WINDOW *w, unsigned i,
-                     gcc_unused unsigned y, unsigned width,
-                     bool selected, const void *data)
-{
-       const auto &list = *(const std::vector<std::string> *)data;
-       char *p = utf8_to_locale(list[i].c_str());
-
-       screen_browser_paint_directory(w, width, selected, p);
-       g_free(p);
-}
-
-/**
- * Paint one item in the album list.  There are two virtual items
- * inserted: at the beginning, there's the special item ".." to go to
- * the parent directory, and at the end, there's the item "All tracks"
- * to view the tracks of all albums.
- */
-static void
-paint_album_callback(WINDOW *w, unsigned i,
-                    gcc_unused unsigned y, unsigned width,
-                    bool selected, const void *data)
-{
-       const auto &list = *(const std::vector<std::string> *)data;
-       const char *p;
-       char *q = nullptr;
-
-       if (i == 0)
-               p = "..";
-       else if (i == list.size() + 1)
-               p = _("All tracks");
-       else
-               p = q = utf8_to_locale(list[i - 1].c_str());
-
-       screen_browser_paint_directory(w, width, selected, p);
-       g_free(q);
-}
-
-void
-ArtistBrowserPage::Paint() const
+const char *
+SongListPage::GetTitle(char *str, size_t size) const
 {
 {
-       switch (mode) {
-       case Mode::ARTISTS:
-               lw.Paint(paint_artist_callback, &artist_list);
-               break;
-
-       case Mode::ALBUMS:
-               lw.Paint(paint_album_callback, &album_list);
-               break;
+       char *s1 = utf8_to_locale(artist.c_str());
+
+       if (IsNulled(album))
+               g_snprintf(str, size,
+                          _("All tracks of artist: %s"), s1);
+       else if (!album.empty()) {
+               char *s2 = utf8_to_locale(album.c_str());
+               g_snprintf(str, size, _("Album: %s - %s"), s1, s2);
+               g_free(s2);
+       } else
+               g_snprintf(str, size,
+                          _("Tracks of no album of artist: %s"), s1);
+       g_free(s1);
 
 
-       case Mode::SONGS:
-               FileListPage::Paint();
-               break;
-       }
+       return str;
 }
 
 }
 
-const char *
-ArtistBrowserPage::GetTitle(char *str, size_t size) const
+bool
+SongListPage::OnCommand(struct mpdclient &c, command_t cmd)
 {
 {
-       switch(mode) {
-               char *s1, *s2;
+       switch(cmd) {
+       case CMD_PLAY:
+               if (lw.selected == 0) {
+                       /* handle ".." */
+                       screen.OnCommand(c, CMD_GO_PARENT_DIRECTORY);
+                       return true;
+               }
 
 
-       case Mode::ARTISTS:
-               g_snprintf(str, size, _("All artists"));
                break;
 
                break;
 
-       case Mode::ALBUMS:
-               s1 = utf8_to_locale(artist.c_str());
-               g_snprintf(str, size, _("Albums of artist: %s"), s1);
-               g_free(s1);
-               break;
+               /* continue and update... */
+       case CMD_SCREEN_UPDATE:
+               LoadSongList(c);
+               return false;
 
 
-       case Mode::SONGS:
-               s1 = utf8_to_locale(artist.c_str());
-
-               if (IsNulled(album))
-                       g_snprintf(str, size,
-                                  _("All tracks of artist: %s"), s1);
-               else if (!album.empty()) {
-                       s2 = utf8_to_locale(album.c_str());
-                       g_snprintf(str, size, _("Album: %s - %s"), s1, s2);
-                       g_free(s2);
-               } else
-                       g_snprintf(str, size,
-                                  _("Tracks of no album of artist: %s"), s1);
-               g_free(s1);
+       default:
                break;
        }
 
                break;
        }
 
-       return str;
+       return FileListPage::OnCommand(c, cmd);
 }
 
 void
 }
 
 void
-ArtistBrowserPage::Update(struct mpdclient &c, unsigned events)
-{
-       if (filelist == nullptr)
-               return;
-
-       if (events & MPD_IDLE_DATABASE)
-               /* the db has changed -> update the list */
-               Reload(c);
-
-       if (events & (MPD_IDLE_DATABASE | MPD_IDLE_QUEUE))
-               screen_browser_sync_highlights(filelist, &c.playlist);
-
-       if (events & (MPD_IDLE_DATABASE
-#ifndef NCMPC_MINI
-                     | MPD_IDLE_QUEUE
-#endif
-                     ))
-               SetDirty();
-}
-
-/* add_query - Add all songs satisfying specified criteria.
-   _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)
+ArtistBrowserPage::OnOpen(struct mpdclient &c)
 {
 {
-       struct mpd_connection *connection = mpdclient_get_connection(c);
-
-       assert(_filter != nullptr);
-
-       if (connection == nullptr)
-               return;
-
-       char *str = utf8_to_locale(_filter);
-       if (table == MPD_TAG_ALBUM)
-               screen_status_printf(_("Adding album %s..."), str);
-       else
-               screen_status_printf(_("Adding %s..."), str);
-       g_free(str);
-
-       mpd_search_db_songs(connection, true);
-       mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                     table, _filter);
-       if (table == MPD_TAG_ALBUM)
-               mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT,
-                                             MPD_TAG_ARTIST, _artist);
-       mpd_search_commit(connection);
-
-       auto *addlist = filelist_new_recv(connection);
+       ProxyPage::OnOpen(c);
 
 
-       if (mpdclient_finish_command(c))
-               mpdclient_filelist_add_all(c, addlist);
-
-       delete addlist;
+       if (GetCurrentPage() == nullptr)
+               SetCurrentPage(c, &artist_list_page);
 }
 
 }
 
-inline bool
-ArtistBrowserPage::OnListCommand(struct mpdclient &c, command_t cmd)
+void
+ArtistBrowserPage::Update(struct mpdclient &c, unsigned events)
 {
 {
-       switch (mode) {
-       case Mode::ARTISTS:
-       case Mode::ALBUMS:
-               if (lw.HandleCommand(cmd)) {
-                       SetDirty();
-                       return true;
-               }
-
-               return false;
+       artist_list_page.AddPendingEvents(events);
+       album_list_page.AddPendingEvents(events);
+       song_list_page.AddPendingEvents(events);
 
 
-       case Mode::SONGS:
-               return FileListPage::OnCommand(c, cmd);
-       }
-
-       assert(0);
-       return 0;
+       ProxyPage::Update(c, events);
 }
 
 bool
 ArtistBrowserPage::OnCommand(struct mpdclient &c, command_t cmd)
 {
 }
 
 bool
 ArtistBrowserPage::OnCommand(struct mpdclient &c, command_t cmd)
 {
-       switch(cmd) {
-               const char *selected;
+       if (ProxyPage::OnCommand(c, cmd))
+               return true;
 
 
+       switch (cmd) {
        case CMD_PLAY:
        case CMD_PLAY:
-               switch (mode) {
-               case Mode::ARTISTS:
-                       if (lw.selected >= artist_list.size())
-                               return true;
-
-                       selected = artist_list[lw.selected].c_str();
-                       OpenAlbumList(c, selected);
-                       lw.Reset();
-
-                       SetDirty();
-                       return true;
-
-               case Mode::ALBUMS:
-                       if (lw.selected == 0) {
-                               /* handle ".." */
-                               OpenArtistList(c, artist.c_str());
-                       } else if (lw.selected == album_list.size() + 1) {
-                               /* handle "show all" */
-                               OpenSongList(c, std::move(artist),
-                                            MakeNulledString());
-                               lw.Reset();
-                       } else {
-                               /* select album */
-                               selected = album_list[lw.selected - 1].c_str();
-                               OpenSongList(c, std::move(artist), selected);
-                               lw.Reset();
-                       }
-
-                       SetDirty();
-                       return true;
-
-               case Mode::SONGS:
-                       if (lw.selected == 0) {
-                               /* handle ".." */
-                               OpenAlbumList(c, std::move(artist),
-                                             IsNulled(album) ? nullptr : album.c_str());
-                               SetDirty();
+               if (GetCurrentPage() == &artist_list_page) {
+                       const char *artist = artist_list_page.GetSelectedValue();
+                       if (artist != nullptr) {
+                               OpenAlbumList(c, artist);
                                return true;
                        }
                                return true;
                        }
-                       break;
-               }
-               break;
-
-               /* FIXME? CMD_GO_* handling duplicates code from CMD_PLAY */
-
-       case CMD_GO_PARENT_DIRECTORY:
-               switch (mode) {
-               case Mode::ARTISTS:
-                       break;
-
-               case Mode::ALBUMS:
-                       OpenArtistList(c, artist.c_str());
-                       break;
-
-               case Mode::SONGS:
-                       OpenAlbumList(c, std::move(artist),
-                                     IsNulled(album) ? nullptr : album.c_str());
-                       break;
+               } 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());
                }
 
                }
 
-               SetDirty();
                break;
 
        case CMD_GO_ROOT_DIRECTORY:
                break;
 
        case CMD_GO_ROOT_DIRECTORY:
-               switch (mode) {
-               case Mode::ARTISTS:
-                       break;
-
-               case Mode::ALBUMS:
-               case Mode::SONGS:
-                       OpenArtistList(c);
-                       lw.Reset();
-                       /* restore first list window state (pop while returning true) */
-                       /* XXX */
-                       break;
-               }
-
-               SetDirty();
-               break;
-
-       case CMD_SELECT:
-       case CMD_ADD:
-               switch(mode) {
-               case Mode::ARTISTS:
-                       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, nullptr);
-                               cmd = CMD_LIST_NEXT; /* continue and select next item... */
-                       }
-                       break;
-
-               case Mode::ALBUMS:
-                       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 = CMD_LIST_NEXT; /* continue and select next item... */
-                               }
-                       }
-                       break;
-
-               case Mode::SONGS:
-                       /* handled by browser_cmd() */
-                       break;
-               }
-               break;
-
-               /* continue and update... */
-       case CMD_SCREEN_UPDATE:
-               Reload(c);
-               return false;
-
-       case CMD_LIST_FIND:
-       case CMD_LIST_RFIND:
-       case CMD_LIST_FIND_NEXT:
-       case CMD_LIST_RFIND_NEXT:
-               switch (mode) {
-               case Mode::ARTISTS:
-                       screen_find(screen, &lw, cmd,
-                                   screen_artist_lw_callback, &artist_list);
-                       SetDirty();
+               if (GetCurrentPage() != &artist_list_page) {
+                       OpenArtistList(c, album_list_page.GetArtist().c_str());
                        return true;
                        return true;
-
-               case Mode::ALBUMS:
-                       screen_find(screen, &lw, cmd,
-                                   AlbumListCallback, &album_list);
-                       SetDirty();
-                       return true;
-
-               case Mode::SONGS:
-                       /* handled by browser_cmd() */
-                       break;
                }
 
                break;
 
                }
 
                break;
 
-       case CMD_LIST_JUMP:
-               switch (mode) {
-               case Mode::ARTISTS:
-                       screen_jump(screen, &lw,
-                                   screen_artist_lw_callback, &artist_list,
-                                   paint_artist_callback, &artist_list);
-                       SetDirty();
+       case CMD_GO_PARENT_DIRECTORY:
+               if (GetCurrentPage() == &album_list_page) {
+                       OpenArtistList(c, album_list_page.GetArtist().c_str());
                        return true;
                        return true;
-
-               case Mode::ALBUMS:
-                       screen_jump(screen, &lw,
-                                   AlbumListCallback, &album_list,
-                                   paint_album_callback, &album_list);
-                       SetDirty();
+               } else if (GetCurrentPage() == &song_list_page) {
+                       const auto &album = song_list_page.GetAlbum();
+                       OpenAlbumList(c, song_list_page.GetArtist(),
+                                     IsNulled(album) ? nullptr : album.c_str());
                        return true;
                        return true;
-
-               case Mode::SONGS:
-                       /* handled by browser_cmd() */
-                       break;
                }
 
                break;
                }
 
                break;
@@ -709,9 +324,6 @@ ArtistBrowserPage::OnCommand(struct mpdclient &c, command_t cmd)
                break;
        }
 
                break;
        }
 
-       if (OnListCommand(c, cmd))
-               return true;
-
        return false;
 }
 
        return false;
 }