feat: initial qobuz-qt source
Lightweight Qt6 desktop client for Qobuz with a Rust audio backend (Symphonia/CPAL via staticlib FFI). Mirrors the spotify-qt layout: toolbar with playback controls, library/context docks on the left, tabbed search side panel on the right, queue panel, now-playing dock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
111
src/view/queuepanel.cpp
Normal file
111
src/view/queuepanel.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "queuepanel.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
static constexpr int UpcomingIndexRole = Qt::UserRole + 1;
|
||||
static constexpr int IsPlayNextRole = Qt::UserRole + 2;
|
||||
|
||||
QueuePanel::QueuePanel(PlayQueue *queue, QWidget *parent)
|
||||
: QDockWidget(tr("Queue"), parent)
|
||||
, m_queue(queue)
|
||||
{
|
||||
setObjectName(QStringLiteral("queuePanel"));
|
||||
setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable);
|
||||
|
||||
auto *container = new QWidget(this);
|
||||
auto *layout = new QVBoxLayout(container);
|
||||
layout->setContentsMargins(4, 4, 4, 4);
|
||||
layout->setSpacing(4);
|
||||
|
||||
// Header row: count label + Clear button
|
||||
auto *headerRow = new QHBoxLayout;
|
||||
m_countLabel = new QLabel(tr("Up next: 0 tracks"), container);
|
||||
m_clearBtn = new QPushButton(tr("Clear"), container);
|
||||
m_clearBtn->setMaximumWidth(64);
|
||||
headerRow->addWidget(m_countLabel, 1);
|
||||
headerRow->addWidget(m_clearBtn);
|
||||
layout->addLayout(headerRow);
|
||||
|
||||
m_list = new QListWidget(container);
|
||||
m_list->setAlternatingRowColors(true);
|
||||
m_list->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
layout->addWidget(m_list, 1);
|
||||
|
||||
setWidget(container);
|
||||
setMinimumWidth(200);
|
||||
|
||||
connect(m_queue, &PlayQueue::queueChanged, this, &QueuePanel::refresh);
|
||||
connect(m_clearBtn, &QPushButton::clicked, this, [this] {
|
||||
m_queue->clearUpcoming();
|
||||
});
|
||||
connect(m_list, &QListWidget::itemDoubleClicked,
|
||||
this, &QueuePanel::onItemDoubleClicked);
|
||||
connect(m_list, &QListWidget::customContextMenuRequested,
|
||||
this, &QueuePanel::onContextMenu);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void QueuePanel::refresh()
|
||||
{
|
||||
m_list->clear();
|
||||
|
||||
const QVector<QJsonObject> upcoming = m_queue->upcomingTracks();
|
||||
const int playNextCount = m_queue->playNextCount();
|
||||
|
||||
m_countLabel->setText(tr("Up next: %1 track(s)").arg(upcoming.size()));
|
||||
m_clearBtn->setEnabled(!upcoming.isEmpty());
|
||||
|
||||
for (int i = 0; i < upcoming.size(); ++i) {
|
||||
const QJsonObject &t = upcoming.at(i);
|
||||
const QString title = t["title"].toString();
|
||||
const QString artist = t["performer"].toObject()["name"].toString().isEmpty()
|
||||
? t["album"].toObject()["artist"].toObject()["name"].toString()
|
||||
: t["performer"].toObject()["name"].toString();
|
||||
|
||||
const QString text = artist.isEmpty()
|
||||
? title
|
||||
: QStringLiteral("%1 — %2").arg(artist, title);
|
||||
|
||||
auto *item = new QListWidgetItem(text, m_list);
|
||||
item->setData(UpcomingIndexRole, i);
|
||||
item->setData(IsPlayNextRole, i < playNextCount);
|
||||
|
||||
// "Play Next" tracks shown slightly differently
|
||||
if (i < playNextCount) {
|
||||
QFont f = item->font();
|
||||
f.setItalic(true);
|
||||
item->setFont(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QueuePanel::onItemDoubleClicked(QListWidgetItem *item)
|
||||
{
|
||||
// Double-clicking an upcoming item is not needed for now (could skip to it later)
|
||||
Q_UNUSED(item)
|
||||
}
|
||||
|
||||
void QueuePanel::onContextMenu(const QPoint &pos)
|
||||
{
|
||||
auto *item = m_list->itemAt(pos);
|
||||
if (!item) return;
|
||||
|
||||
const int idx = item->data(UpcomingIndexRole).toInt();
|
||||
|
||||
QMenu menu(this);
|
||||
auto *removeAct = menu.addAction(tr("Remove from queue"));
|
||||
auto *toTopAct = menu.addAction(tr("Move to top (play next)"));
|
||||
|
||||
connect(removeAct, &QAction::triggered, this, [this, idx] {
|
||||
m_queue->removeUpcoming(idx);
|
||||
});
|
||||
connect(toTopAct, &QAction::triggered, this, [this, idx] {
|
||||
m_queue->moveUpcomingToTop(idx);
|
||||
});
|
||||
|
||||
menu.exec(m_list->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
Reference in New Issue
Block a user