From 1d90dff7796d557e68cd5b227174f2ed93b45aed Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 12 Feb 2026 17:23:08 +0100 Subject: [PATCH] refactor: Remove duplication explore and home model --- src/CMakeLists.txt | 1 + src/abstractshelfmodel.cpp | 194 +++++++++++++++++++++++++++++++++++++ src/abstractshelfmodel.h | 76 +++++++++++++++ src/exploremodel.cpp | 39 +------- src/exploremodel.h | 23 +---- src/homemodel.cpp | 182 +--------------------------------- src/homemodel.h | 67 +------------ 7 files changed, 283 insertions(+), 299 deletions(-) create mode 100644 src/abstractshelfmodel.cpp create mode 100644 src/abstractshelfmodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a9dce018..45512d25 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable(audiotube thumbnailsource.cpp abstractytmusicmodel.cpp multiiterableview.h + abstractshelfmodel.cpp homemodel.cpp exploremodel.cpp library.cpp diff --git a/src/abstractshelfmodel.cpp b/src/abstractshelfmodel.cpp new file mode 100644 index 00000000..7d2ea9db --- /dev/null +++ b/src/abstractshelfmodel.cpp @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: 2026 Tanveer Ahmed Mansuri +// SPDX-FileCopyrightText: 2026 Carl Schwan +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "abstractshelfmodel.h" +#include "playlistutils.h" +#include "asyncytmusic.h" +#include + +// --- HomeShelfModel --- + +HomeShelfModel::HomeShelfModel(QObject *parent) + : QAbstractListModel(parent) {} + +void HomeShelfModel::setItems(std::vector items) { + beginResetModel(); + m_items = std::move(items); + endResetModel(); +} + +int HomeShelfModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : int(m_items.size()); +} + +QVariant HomeShelfModel::data(const QModelIndex &index, int role) const { + if (!index.isValid() || index.row() >= int(m_items.size())) return {}; + + const auto &item = m_items.at(index.row()); + + switch (role) { + case Title: + return QString::fromStdString(std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { + return arg.title; + } else if constexpr (std::is_same_v) { + return arg.artist; + } else if constexpr (std::is_same_v) { + if (arg.title) return *arg.title; + if (!arg.artists.empty()) return arg.artists.front().name; + return std::string(); + } else if constexpr (std::is_same_v) { + return arg.title; + } else { + return std::string(); + } + }, item)); + case TypeRole: + // Use SearchModel::Type values for compatibility if needed, but we have our own enum if we want. + // For simplicity, let's just return what SearchModel would return or map to strings if QML expects that. + // Looking at SearchModel, it returns SearchModel::Type enum values. + // But HomeShelfModel has its own enum for roles. + // Wait, SearchModel::Type is registered in SearchModel. I should reuse SearchModel::Type if I want to use same delegates? + // Actually, let's just use int values corresponding to SearchModel::Type to be safe, or cast. + return std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) return SearchModel::Album; + if constexpr (std::is_same_v) return SearchModel::Artist; + if constexpr (std::is_same_v) return SearchModel::Playlist; + if constexpr (std::is_same_v) return SearchModel::Song; + if constexpr (std::is_same_v) return SearchModel::Video; + if constexpr (std::is_same_v) return SearchModel::TopResult; + if constexpr (std::is_same_v) return SearchModel::Mood; + return SearchModel::Song; // Default? + }, item); + case VideoId: + return std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v) return QString::fromStdString(arg.video_id); + if constexpr (std::is_same_v) return arg.video_id ? QString::fromStdString(*arg.video_id) : QString(); + return QString(); + }, item); + case Artists: + return QVariant::fromValue(std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) return arg.artists; + return std::vector(); + }, item)); + case RadioPlaylistId: + return std::visit([&](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.radio_id) return QString::fromStdString(*arg.radio_id); + } + return QString(); + }, item); + case ThumbnailUrl: + return std::visit([&](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return QString(); + } else { + if (!arg.thumbnails.empty()) { + return QString::fromStdString(arg.thumbnails.front().url); + } + } + return QString(); + }, item); + case ArtistsDisplayString: + return QVariant::fromValue(std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + return PlaylistUtils::artistsToString(arg.artists); + } else if constexpr(std::is_same_v) { + if (arg.title) return PlaylistUtils::artistsToString(arg.artists); + } + return QString(); + }, item)); + } + return {}; +} + +QHash HomeShelfModel::roleNames() const { + return { + {Title, "title"}, + {TypeRole, "type"}, + {VideoId, "videoId"}, + {Artists, "artists"}, + {ArtistsDisplayString, "artistsDisplayString"}, + {RadioPlaylistId, "radioPlaylistId"}, + {ThumbnailUrl, "thumbnailUrl"} + }; +} + +void HomeShelfModel::triggerItem(int row) { + if (row < 0 || row >= int(m_items.size())) return; + + std::visit([&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + if (arg.browse_id) { + Q_EMIT openAlbum(QString::fromStdString(*arg.browse_id)); + } + } else if constexpr (std::is_same_v) { + Q_EMIT openArtist(QString::fromStdString(arg.browse_id), QString::fromStdString(arg.radio_id.value_or("")), QString::fromStdString(arg.shuffle_id.value_or(""))); + } else if constexpr (std::is_same_v) { + Q_EMIT openPlaylist(QString::fromStdString(arg.browse_id)); + } else if constexpr (std::is_same_v) { + Q_EMIT openSong(QString::fromStdString(arg.video_id)); + } else if constexpr (std::is_same_v) { + Q_EMIT openVideo(QString::fromStdString(arg.video_id), QString::fromStdString(arg.title)); + } else if constexpr (std::is_same_v) { + if (arg.video_id) { + Q_EMIT openSong(QString::fromStdString(*arg.video_id)); + } + else if (!arg.artists.empty() && arg.artists.front().id) + Q_EMIT openArtist(QString::fromStdString(*arg.artists.front().id), {}, {}); + } else if constexpr (std::is_same_v) { + Q_EMIT openMood(QString::fromStdString(arg.title), QString::fromStdString(arg.params)); + } + }, m_items.at(row)); +} + +AbstractShelfModel::AbstractShelfModel(QObject *parent) + : AbstractYTMusicModel(parent) +{ +} + +int AbstractShelfModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : int(m_shelves.size()); +} + +QVariant AbstractShelfModel::data(const QModelIndex &index, int role) const { + if (!index.isValid() || index.row() >= int(m_shelves.size())) return {}; + + switch (role) { + case Title: + return QString::fromStdString(m_shelves.at(index.row()).title); + case ContentModelRole: + return QVariant::fromValue(static_cast(m_shelfModels.at(index.row()).get())); + } + return {}; +} + +QHash AbstractShelfModel::roleNames() const { + return { + {Title, "title"}, + {ContentModelRole, "contentModel"} + }; +} + +void AbstractShelfModel::addShelf(const std::vector &contents) { + auto model = std::make_unique(this); + model->setItems(contents); + + connect(model.get(), &HomeShelfModel::openAlbum, this, &AbstractShelfModel::openAlbum); + connect(model.get(), &HomeShelfModel::openArtist, this, &AbstractShelfModel::openArtist); + connect(model.get(), &HomeShelfModel::openPlaylist, this, &AbstractShelfModel::openPlaylist); + connect(model.get(), &HomeShelfModel::openSong, this, &AbstractShelfModel::openSong); + connect(model.get(), &HomeShelfModel::openVideo, this, &AbstractShelfModel::openVideo); + connect(model.get(), &HomeShelfModel::openMood, this, &AbstractShelfModel::openMood); + + m_shelfModels.push_back(std::move(model)); +} diff --git a/src/abstractshelfmodel.h b/src/abstractshelfmodel.h new file mode 100644 index 00000000..87f1de6e --- /dev/null +++ b/src/abstractshelfmodel.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2026 Tanveer Ahmed Mansuri +// SPDX-FileCopyrightText: 2026 Carl Schwan +// +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include "abstractytmusicmodel.h" +#include "ytmusic.h" +#include "searchmodel.h" + +// Model for items within a shelf (horizontal list) +class HomeShelfModel : public QAbstractListModel { + Q_OBJECT + QML_ELEMENT +public: + enum Role { + Title = Qt::UserRole + 1, + TypeRole, + VideoId, + Artists, + RadioPlaylistId, + ThumbnailUrl, + ArtistsDisplayString + }; + Q_ENUM(Role) + + explicit HomeShelfModel(QObject *parent = nullptr); + void setItems(std::vector items); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + Q_INVOKABLE void triggerItem(int row); + + Q_SIGNAL void openAlbum(const QString &browseId); + Q_SIGNAL void openArtist(const QString &browseId, const QString &radioId, const QString &shuffleId); + Q_SIGNAL void openPlaylist(const QString &browseId); + Q_SIGNAL void openSong(const QString &videoId); + Q_SIGNAL void openVideo(const QString &videoId, const QString &title); + Q_SIGNAL void openMood(const QString &title, const QString ¶ms); + +private: + std::vector m_items; +}; + +class AbstractShelfModel : public AbstractYTMusicModel { + Q_OBJECT + +public: + enum Role { + Title = Qt::UserRole + 1, + ContentModelRole + }; + + explicit AbstractShelfModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + // Re-expose signals from shelf models + Q_SIGNAL void openAlbum(const QString &browseId); + Q_SIGNAL void openArtist(const QString &browseId, const QString &radioId, const QString &shuffleId); + Q_SIGNAL void openPlaylist(const QString &browseId); + Q_SIGNAL void openSong(const QString &videoId); + Q_SIGNAL void openVideo(const QString &videoId, const QString &title); + Q_SIGNAL void openMood(const QString &title, const QString ¶ms); + +protected: + void addShelf(const std::vector &contents); + + std::vector m_shelves; + std::vector> m_shelfModels; +}; diff --git a/src/exploremodel.cpp b/src/exploremodel.cpp index 7db25deb..527d60ca 100644 --- a/src/exploremodel.cpp +++ b/src/exploremodel.cpp @@ -9,33 +9,12 @@ // --- ExploreModel --- -ExploreModel::ExploreModel(QObject *parent) : AbstractYTMusicModel(parent) { +ExploreModel::ExploreModel(QObject *parent) + : AbstractShelfModel(parent) +{ refresh(); } -int ExploreModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : int(m_shelves.size()); -} - -QVariant ExploreModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= int(m_shelves.size())) return {}; - - switch (role) { - case Title: - return QString::fromStdString(m_shelves.at(index.row()).title); - case ContentModelRole: - return QVariant::fromValue(static_cast(m_shelfModels.at(index.row()).get())); - } - return {}; -} - -QHash ExploreModel::roleNames() const { - return { - {Title, "title"}, - {ContentModelRole, "contentModel"} - }; -} - void ExploreModel::refresh() { setLoading(true); @@ -55,17 +34,7 @@ void ExploreModel::refresh() { m_shelfModels.clear(); for (const auto &shelf : m_shelves) { - auto model = std::make_unique(this); - model->setItems(shelf.contents); - - connect(model.get(), &HomeShelfModel::openAlbum, this, &ExploreModel::openAlbum); - connect(model.get(), &HomeShelfModel::openArtist, this, &ExploreModel::openArtist); - connect(model.get(), &HomeShelfModel::openPlaylist, this, &ExploreModel::openPlaylist); - connect(model.get(), &HomeShelfModel::openSong, this, &ExploreModel::openSong); - connect(model.get(), &HomeShelfModel::openVideo, this, &ExploreModel::openVideo); - connect(model.get(), &HomeShelfModel::openMood, this, &ExploreModel::openMood); - - m_shelfModels.push_back(std::move(model)); + addShelf(shelf.contents); } endResetModel(); setLoading(false); diff --git a/src/exploremodel.h b/src/exploremodel.h index c4b96775..715b88d7 100644 --- a/src/exploremodel.h +++ b/src/exploremodel.h @@ -11,11 +11,10 @@ #include "abstractytmusicmodel.h" #include "ytmusic.h" #include "homemodel.h" - -// Reuse HomeShelfModel +#include "abstractshelfmodel.h" // Main model for the Explore tab -class ExploreModel : public AbstractYTMusicModel { +class ExploreModel : public AbstractShelfModel { Q_OBJECT QML_ELEMENT @@ -27,23 +26,7 @@ public: explicit ExploreModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; - - Q_INVOKABLE void refresh(); - - // Re-expose signals from shelf models - Q_SIGNAL void openAlbum(const QString &browseId); - Q_SIGNAL void openArtist(const QString &browseId, const QString &radioId, const QString &shuffleId); - Q_SIGNAL void openPlaylist(const QString &browseId); - Q_SIGNAL void openSong(const QString &videoId); - Q_SIGNAL void openVideo(const QString &videoId, const QString &title); - Q_SIGNAL void openMood(const QString &title, const QString ¶ms); - -private: - std::vector m_shelves; - std::vector> m_shelfModels; + void refresh(); }; // Model for Mood Playlists diff --git a/src/homemodel.cpp b/src/homemodel.cpp index 33c5dc7b..6de3b5f8 100644 --- a/src/homemodel.cpp +++ b/src/homemodel.cpp @@ -3,185 +3,17 @@ // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #include "homemodel.h" -#include "playlistutils.h" #include "asyncytmusic.h" #include -// --- HomeShelfModel --- - -HomeShelfModel::HomeShelfModel(QObject *parent) - : QAbstractListModel(parent) {} - -void HomeShelfModel::setItems(std::vector items) { - beginResetModel(); - m_items = std::move(items); - endResetModel(); -} - -int HomeShelfModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : int(m_items.size()); -} - -QVariant HomeShelfModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= int(m_items.size())) return {}; - - const auto &item = m_items.at(index.row()); - - switch (role) { - case Title: - return QString::fromStdString(std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { - return arg.title; - } else if constexpr (std::is_same_v) { - return arg.artist; - } else if constexpr (std::is_same_v) { - if (arg.title) return *arg.title; - if (!arg.artists.empty()) return arg.artists.front().name; - return std::string(); - } else if constexpr (std::is_same_v) { - return arg.title; - } else { - return std::string(); - } - }, item)); - case TypeRole: - // Use SearchModel::Type values for compatibility if needed, but we have our own enum if we want. - // For simplicity, let's just return what SearchModel would return or map to strings if QML expects that. - // Looking at SearchModel, it returns SearchModel::Type enum values. - // But HomeShelfModel has its own enum for roles. - // Wait, SearchModel::Type is registered in SearchModel. I should reuse SearchModel::Type if I want to use same delegates? - // Actually, let's just use int values corresponding to SearchModel::Type to be safe, or cast. - return std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) return SearchModel::Album; - if constexpr (std::is_same_v) return SearchModel::Artist; - if constexpr (std::is_same_v) return SearchModel::Playlist; - if constexpr (std::is_same_v) return SearchModel::Song; - if constexpr (std::is_same_v) return SearchModel::Video; - if constexpr (std::is_same_v) return SearchModel::TopResult; - if constexpr (std::is_same_v) return SearchModel::Mood; - return SearchModel::Song; // Default? - }, item); - case VideoId: - return std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v || std::is_same_v) return QString::fromStdString(arg.video_id); - if constexpr (std::is_same_v) return arg.video_id ? QString::fromStdString(*arg.video_id) : QString(); - return QString(); - }, item); - case Artists: - return QVariant::fromValue(std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) return arg.artists; - return std::vector(); - }, item)); - case RadioPlaylistId: - return std::visit([&](auto &&arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - if (arg.radio_id) return QString::fromStdString(*arg.radio_id); - } - return QString(); - }, item); - case ThumbnailUrl: - return std::visit([&](auto &&arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return QString(); - } else { - if (!arg.thumbnails.empty()) { - return QString::fromStdString(arg.thumbnails.front().url); - } - } - return QString(); - }, item); - case ArtistsDisplayString: - return QVariant::fromValue(std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - return PlaylistUtils::artistsToString(arg.artists); - } else if constexpr(std::is_same_v) { - if (arg.title) return PlaylistUtils::artistsToString(arg.artists); - } - return QString(); - }, item)); - } - return {}; -} - -QHash HomeShelfModel::roleNames() const { - return { - {Title, "title"}, - {TypeRole, "type"}, - {VideoId, "videoId"}, - {Artists, "artists"}, - {ArtistsDisplayString, "artistsDisplayString"}, - {RadioPlaylistId, "radioPlaylistId"}, - {ThumbnailUrl, "thumbnailUrl"} - }; -} - -void HomeShelfModel::triggerItem(int row) { - if (row < 0 || row >= int(m_items.size())) return; - - std::visit([&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - if (arg.browse_id) { - Q_EMIT openAlbum(QString::fromStdString(*arg.browse_id)); - } - } else if constexpr (std::is_same_v) { - Q_EMIT openArtist(QString::fromStdString(arg.browse_id), QString::fromStdString(arg.radio_id.value_or("")), QString::fromStdString(arg.shuffle_id.value_or(""))); - } else if constexpr (std::is_same_v) { - Q_EMIT openPlaylist(QString::fromStdString(arg.browse_id)); - } else if constexpr (std::is_same_v) { - Q_EMIT openSong(QString::fromStdString(arg.video_id)); - } else if constexpr (std::is_same_v) { - Q_EMIT openVideo(QString::fromStdString(arg.video_id), QString::fromStdString(arg.title)); - } else if constexpr (std::is_same_v) { - if (arg.video_id) { - Q_EMIT openSong(QString::fromStdString(*arg.video_id)); - } - else if (!arg.artists.empty() && arg.artists.front().id) - Q_EMIT openArtist(QString::fromStdString(*arg.artists.front().id), {}, {}); - } else if constexpr (std::is_same_v) { - Q_EMIT openMood(QString::fromStdString(arg.title), QString::fromStdString(arg.params)); - } - }, m_items.at(row)); -} - // --- HomeModel --- HomeModel::HomeModel(QObject *parent) - : AbstractYTMusicModel(parent) + : AbstractShelfModel(parent) { refresh(); } -int HomeModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : int(m_shelves.size()); -} - -QVariant HomeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= int(m_shelves.size())) return {}; - - switch (role) { - case Title: - return QString::fromStdString(m_shelves.at(index.row()).title); - case ContentModelRole: - return QVariant::fromValue(static_cast(m_shelfModels.at(index.row()).get())); - } - return {}; -} - -QHash HomeModel::roleNames() const { - return { - {Title, "title"}, - {ContentModelRole, "contentModel"} - }; -} - void HomeModel::refresh() { setLoading(true); // Fetch more shelves to ensure we find "Quick picks" @@ -201,17 +33,7 @@ void HomeModel::refresh() { m_shelves = std::move(result); m_shelfModels.clear(); for (const auto &shelf : m_shelves) { - auto model = std::make_unique(this); - model->setItems(shelf.contents); - - // Connect signals from shelf model to HomeModel signals - connect(model.get(), &HomeShelfModel::openAlbum, this, &HomeModel::openAlbum); - connect(model.get(), &HomeShelfModel::openArtist, this, &HomeModel::openArtist); - connect(model.get(), &HomeShelfModel::openPlaylist, this, &HomeModel::openPlaylist); - connect(model.get(), &HomeShelfModel::openSong, this, &HomeModel::openSong); - connect(model.get(), &HomeShelfModel::openVideo, this, &HomeModel::openVideo); - - m_shelfModels.push_back(std::move(model)); + addShelf(shelf.contents); } endResetModel(); setLoading(false); diff --git a/src/homemodel.h b/src/homemodel.h index fe2b9f57..335f76f5 100644 --- a/src/homemodel.h +++ b/src/homemodel.h @@ -4,78 +4,17 @@ #pragma once -#include #include -#include -#include "abstractytmusicmodel.h" -#include "ytmusic.h" -#include "searchmodel.h" - -// Model for items within a shelf (horizontal list) -class HomeShelfModel : public QAbstractListModel { - Q_OBJECT - QML_ELEMENT -public: - enum Role { - Title = Qt::UserRole + 1, - TypeRole, - VideoId, - Artists, - RadioPlaylistId, - ThumbnailUrl, - ArtistsDisplayString - }; - Q_ENUM(Role) - - explicit HomeShelfModel(QObject *parent = nullptr); - void setItems(std::vector items); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; - - Q_INVOKABLE void triggerItem(int row); - - Q_SIGNAL void openAlbum(const QString &browseId); - Q_SIGNAL void openArtist(const QString &browseId, const QString &radioId, const QString &shuffleId); - Q_SIGNAL void openPlaylist(const QString &browseId); - Q_SIGNAL void openSong(const QString &videoId); - Q_SIGNAL void openVideo(const QString &videoId, const QString &title); - Q_SIGNAL void openMood(const QString &title, const QString ¶ms); - -private: - std::vector m_items; -}; +#include "abstractshelfmodel.h" // Main model for the Home tab (vertical list of shelves) -class HomeModel : public AbstractYTMusicModel { +class HomeModel : public AbstractShelfModel { Q_OBJECT QML_ELEMENT public: - enum Role { - Title = Qt::UserRole + 1, - ContentModelRole - }; - explicit HomeModel(QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; - - Q_INVOKABLE void refresh(); - - // Re-expose signals from shelf models - Q_SIGNAL void openAlbum(const QString &browseId); - Q_SIGNAL void openArtist(const QString &browseId, const QString &radioId, const QString &shuffleId); - Q_SIGNAL void openPlaylist(const QString &browseId); - Q_SIGNAL void openSong(const QString &videoId); - Q_SIGNAL void openVideo(const QString &videoId, const QString &title); - Q_SIGNAL void openMood(const QString &title, const QString ¶ms); - -private: - std::vector m_shelves; - std::vector> m_shelfModels; + void refresh(); };