Files
qobuz-qt/src/list/library.cpp
joren 4ebd5ed3f0
Some checks failed
Build for Windows / build-windows (push) Has been cancelled
fix: detect followed playlists correctly in header
Load owner+subscriber playlists, track all playlist IDs for follow-state resolution, and keep audio ring buffer at 32k as requested.
2026-03-31 02:02:31 +02:00

206 lines
6.8 KiB
C++

#include "library.hpp"
#include <QHeaderView>
#include <QJsonArray>
#include <QJsonObject>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
namespace List
{
static constexpr int TypeRole = Qt::UserRole + 1;
static constexpr int IdRole = Qt::UserRole + 2;
static constexpr int NameRole = Qt::UserRole + 3;
static constexpr int IsOwnerRole = Qt::UserRole + 4;
enum NodeType {
NodeFavTracks,
NodeFavAlbums,
NodeFavArtists,
NodePlaylist,
NodeBrowseGenres,
NodeBrowsePlaylists,
};
Library::Library(QobuzBackend *backend, QWidget *parent)
: QTreeWidget(parent)
, m_backend(backend)
{
setHeaderHidden(true);
setRootIsDecorated(true);
setContextMenuPolicy(Qt::CustomContextMenu);
buildStaticNodes();
connect(m_backend, &QobuzBackend::userPlaylistsLoaded,
this, &Library::onUserPlaylistsLoaded);
connect(m_backend, &QobuzBackend::playlistCreated,
this, [this](const QJsonObject &) { refresh(); });
connect(m_backend, &QobuzBackend::playlistDeleted,
this, [this](const QJsonObject &result) {
const qint64 deletedId = static_cast<qint64>(result["playlist_id"].toDouble());
if (deletedId == m_openPlaylistId) {
m_openPlaylistId = 0;
emit openPlaylistDeleted();
}
refresh();
});
connect(this, &QTreeWidget::itemClicked,
this, &Library::onItemClicked);
connect(this, &QTreeWidget::itemDoubleClicked,
this, &Library::onItemDoubleClicked);
connect(this, &QTreeWidget::customContextMenuRequested,
this, &Library::onContextMenuRequested);
}
void Library::buildStaticNodes()
{
// My Library
m_myLibNode = new QTreeWidgetItem(this, QStringList{tr("My Library")});
m_myLibNode->setExpanded(true);
auto *tracksItem = new QTreeWidgetItem(m_myLibNode, QStringList{tr("Favorite Tracks")});
tracksItem->setData(0, TypeRole, NodeFavTracks);
auto *albumsItem = new QTreeWidgetItem(m_myLibNode, QStringList{tr("Favorite Albums")});
albumsItem->setData(0, TypeRole, NodeFavAlbums);
auto *artistsItem = new QTreeWidgetItem(m_myLibNode, QStringList{tr("Favorite Artists")});
artistsItem->setData(0, TypeRole, NodeFavArtists);
// Playlists
m_playlistsNode = new QTreeWidgetItem(this, QStringList{tr("Playlists")});
m_playlistsNode->setExpanded(true);
// Browse
m_browseNode = new QTreeWidgetItem(this, QStringList{tr("Browse")});
m_browseNode->setExpanded(true);
auto *genresItem = new QTreeWidgetItem(m_browseNode, QStringList{tr("Genres")});
genresItem->setData(0, TypeRole, NodeBrowseGenres);
auto *playlistsItem = new QTreeWidgetItem(m_browseNode, QStringList{tr("Playlists")});
playlistsItem->setData(0, TypeRole, NodeBrowsePlaylists);
}
void Library::refresh()
{
// Remove old playlist children
while (m_playlistsNode->childCount() > 0)
delete m_playlistsNode->takeChild(0);
m_backend->getUserPlaylists();
}
void Library::onUserPlaylistsLoaded(const QJsonObject &result)
{
while (m_playlistsNode->childCount() > 0)
delete m_playlistsNode->takeChild(0);
QSet<qint64> allPlaylistIds;
QVector<QPair<qint64, QString>> editablePlaylists;
const qint64 myUserId = AppSettings::instance().userId();
const QJsonArray items = result["items"].toArray();
for (const auto &v : items) {
const QJsonObject pl = v.toObject();
const QString name = pl["name"].toString();
const qint64 id = static_cast<qint64>(pl["id"].toDouble());
const qint64 ownId = static_cast<qint64>(pl["owner"].toObject()["id"].toDouble());
const bool isOwner = (myUserId > 0 && ownId == myUserId);
if (id > 0)
allPlaylistIds.insert(id);
auto *item = new QTreeWidgetItem(m_playlistsNode, QStringList{name});
item->setData(0, TypeRole, NodePlaylist);
item->setData(0, IdRole, id);
item->setData(0, NameRole, name);
item->setData(0, IsOwnerRole, isOwner);
// Only include playlists we can edit in the "Add to playlist" submenu
if (isOwner)
editablePlaylists.append({id, name});
}
emit userPlaylistIdsChanged(allPlaylistIds);
emit userPlaylistsChanged(editablePlaylists);
}
void Library::onContextMenuRequested(const QPoint &pos)
{
QTreeWidgetItem *item = itemAt(pos);
if (!item) return;
const bool isHeader = (item == m_playlistsNode);
const bool isPlaylistItem = (!isHeader && item->parent() == m_playlistsNode &&
item->data(0, TypeRole).toInt() == NodePlaylist);
if (!isHeader && !isPlaylistItem) return;
QMenu menu(this);
auto *newPl = menu.addAction(tr("New Playlist…"));
connect(newPl, &QAction::triggered, this, [this] {
bool ok = false;
const QString name = QInputDialog::getText(
this, tr("New Playlist"), tr("Playlist name:"),
QLineEdit::Normal, QString(), &ok);
if (ok && !name.trimmed().isEmpty())
m_backend->createPlaylist(name.trimmed());
});
if (isPlaylistItem) {
const qint64 plId = item->data(0, IdRole).toLongLong();
const QString plName = item->data(0, NameRole).toString();
const bool isOwner = item->data(0, IsOwnerRole).toBool();
if (isOwner) {
menu.addSeparator();
auto *delPl = menu.addAction(tr("Delete \"%1\"").arg(plName));
connect(delPl, &QAction::triggered, this, [this, plId, plName] {
const auto answer = QMessageBox::question(
this,
tr("Delete Playlist"),
tr("Permanently delete \"%1\"? This cannot be undone.").arg(plName),
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel);
if (answer == QMessageBox::Yes)
m_backend->deletePlaylist(plId);
});
}
}
menu.exec(viewport()->mapToGlobal(pos));
}
void Library::onItemClicked(QTreeWidgetItem *item, int)
{
if (!item) return;
const int type = item->data(0, TypeRole).toInt();
switch (type) {
case NodeFavTracks: emit favTracksRequested(); break;
case NodeFavAlbums: emit favAlbumsRequested(); break;
case NodeFavArtists: emit favArtistsRequested(); break;
case NodeBrowseGenres: emit browseGenresRequested(); break;
case NodeBrowsePlaylists: emit browsePlaylistsRequested(); break;
case NodePlaylist: {
const qint64 id = item->data(0, IdRole).toLongLong();
const QString name = item->data(0, NameRole).toString();
m_openPlaylistId = id;
emit playlistRequested(id, name);
break;
}
default: break;
}
}
void Library::onItemDoubleClicked(QTreeWidgetItem *item, int column)
{
onItemClicked(item, column);
}
} // namespace List