#include "view.hpp" #include #include #include #include #include #include static constexpr int IdRole = Qt::UserRole + 1; static constexpr int TypeRole = Qt::UserRole + 2; namespace SidePanel { // ---- SearchTab ---- SearchTab::SearchTab(QobuzBackend *backend, QWidget *parent) : QWidget(parent) , m_backend(backend) { auto *layout = new QVBoxLayout(this); layout->setContentsMargins(4, 4, 4, 4); // Search bar auto *barLayout = new QHBoxLayout; m_searchBox = new QLineEdit(this); m_searchBox->setPlaceholderText(tr("Search Qobuz…")); m_searchBox->setClearButtonEnabled(true); auto *searchBtn = new QPushButton(tr("Go"), this); barLayout->addWidget(m_searchBox); barLayout->addWidget(searchBtn); layout->addLayout(barLayout); // Result tabs m_resultTabs = new QTabWidget(this); m_trackResults = new QTreeWidget(this); m_trackResults->setHeaderLabels({tr("Title"), tr("Artist"), tr("Album")}); m_trackResults->setRootIsDecorated(false); m_albumResults = new QTreeWidget(this); m_albumResults->setHeaderLabels({tr(""), tr("Album"), tr("Artist")}); m_albumResults->setRootIsDecorated(false); m_albumResults->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); m_albumResults->header()->setSectionResizeMode(1, QHeaderView::Stretch); m_albumResults->header()->setSectionResizeMode(2, QHeaderView::Stretch); m_albumResults->header()->setStretchLastSection(false); m_artistResults = new QTreeWidget(this); m_artistResults->setHeaderLabels({tr("Artist")}); m_artistResults->setRootIsDecorated(false); m_resultTabs->addTab(m_trackResults, tr("Tracks")); m_resultTabs->addTab(m_albumResults, tr("Albums")); m_resultTabs->addTab(m_artistResults, tr("Artists")); layout->addWidget(m_resultTabs); connect(searchBtn, &QPushButton::clicked, this, &SearchTab::onSearchSubmit); connect(m_searchBox, &QLineEdit::returnPressed, this, &SearchTab::onSearchSubmit); connect(m_backend, &QobuzBackend::searchResult, this, &SearchTab::onSearchResult); connect(m_trackResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); connect(m_albumResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); connect(m_artistResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); } void SearchTab::onSearchSubmit() { const QString q = m_searchBox->text().trimmed(); if (!q.isEmpty()) m_backend->search(q, 0, 20); } void SearchTab::onSearchResult(const QJsonObject &result) { // Populate tracks m_trackResults->clear(); const QJsonArray tracks = result["tracks"].toObject()["items"].toArray(); for (const auto &v : tracks) { const QJsonObject t = v.toObject(); const QString performer = t["performer"].toObject()["name"].toString(); const QString album = t["album"].toObject()["title"].toString(); auto *item = new QTreeWidgetItem(m_trackResults, QStringList{t["title"].toString(), performer, album}); item->setData(0, IdRole, static_cast(t["id"].toDouble())); item->setData(0, TypeRole, QStringLiteral("track")); } // Populate albums m_albumResults->clear(); { QFont hiResFont; hiResFont.setBold(true); hiResFont.setPointSizeF(hiResFont.pointSizeF() * 0.85); const QJsonArray albums = result["albums"].toObject()["items"].toArray(); for (const auto &v : albums) { const QJsonObject a = v.toObject(); const QString artist = a["artist"].toObject()["name"].toString(); const bool hiRes = a["hires_streamable"].toBool(); auto *item = new QTreeWidgetItem(m_albumResults, QStringList{QString(), a["title"].toString(), artist}); if (hiRes) { item->setText(0, QStringLiteral("H")); item->setForeground(0, QColor(QStringLiteral("#FFD700"))); item->setFont(0, hiResFont); item->setTextAlignment(0, Qt::AlignCenter); } item->setData(0, TypeRole, QStringLiteral("album")); // handler reads col 0 item->setData(1, IdRole, a["id"].toString()); } } // Populate artists m_artistResults->clear(); const QJsonArray artists = result["artists"].toObject()["items"].toArray(); for (const auto &v : artists) { const QJsonObject ar = v.toObject(); auto *item = new QTreeWidgetItem(m_artistResults, QStringList{ar["name"].toString()}); item->setData(0, IdRole, static_cast(ar["id"].toDouble())); item->setData(0, TypeRole, QStringLiteral("artist")); } } void SearchTab::onItemDoubleClicked(QTreeWidgetItem *item, int) { if (!item) return; const QString type = item->data(0, TypeRole).toString(); if (type == QStringLiteral("track")) { emit trackPlayRequested(item->data(0, IdRole).toLongLong()); } else if (type == QStringLiteral("album")) { emit albumSelected(item->data(1, IdRole).toString()); } else if (type == QStringLiteral("artist")) { emit artistSelected(item->data(0, IdRole).toLongLong()); } } // ---- View ---- View::View(QobuzBackend *backend, QWidget *parent) : QDockWidget(tr("Search"), parent) { setObjectName(QStringLiteral("searchPanel")); setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable); m_search = new SearchTab(backend, this); setWidget(m_search); connect(m_search, &SearchTab::albumSelected, this, &View::albumSelected); connect(m_search, &SearchTab::artistSelected, this, &View::artistSelected); connect(m_search, &SearchTab::trackPlayRequested, this, &View::trackPlayRequested); } } // namespace SidePanel