#include "maintoolbar.hpp" #include "../util/settings.hpp" #include "../model/tracklistmodel.hpp" #include #include #include MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *parent) : QToolBar(parent) , m_backend(backend) , m_queue(queue) { setMovable(false); setFloatable(false); setContextMenuPolicy(Qt::PreventContextMenu); setIconSize(QSize(22, 22)); m_nam = new QNetworkAccessManager(this); connect(m_nam, &QNetworkAccessManager::finished, this, &MainToolBar::onAlbumArtReady); // --- Album art thumbnail --- m_artLabel = new QLabel(this); m_artLabel->setFixedSize(36, 36); m_artLabel->setScaledContents(true); m_artLabel->setStyleSheet("border: 1px solid #444; background: #1a1a1a;"); m_artLabel->setPixmap(QIcon(":/res/icons/view-media-album-cover.svg") .pixmap(32, 32)); addWidget(m_artLabel); addSeparator(); // --- Playback controls --- m_previous = addAction(Icon::previous(), tr("Previous")); connect(m_previous, &QAction::triggered, this, &MainToolBar::onPrevious); m_playPause = addAction(Icon::play(), tr("Play")); connect(m_playPause, &QAction::triggered, this, &MainToolBar::onPlayPause); m_next = addAction(Icon::next(), tr("Next")); connect(m_next, &QAction::triggered, this, &MainToolBar::onNext); addSeparator(); // --- Track info label --- m_trackLabel = new QLabel(tr("Not playing"), this); m_trackLabel->setMinimumWidth(180); m_trackLabel->setMaximumWidth(340); m_trackLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); addWidget(m_trackLabel); addSeparator(); // --- Progress slider --- m_progress = new ClickableSlider(Qt::Horizontal, this); m_progress->setRange(0, 1000); m_progress->setValue(0); m_progress->setMinimumWidth(160); m_progress->setMaximumWidth(380); addWidget(m_progress); connect(m_progress, &QSlider::sliderPressed, this, [this] { m_seeking = true; }); connect(m_progress, &QSlider::sliderReleased, this, &MainToolBar::onProgressReleased); m_timeLabel = new QLabel("0:00 / 0:00", this); addWidget(m_timeLabel); addSeparator(); // --- Volume --- m_volume = new VolumeButton(this); m_volume->setValue(AppSettings::instance().volume()); addWidget(m_volume); connect(m_volume, &VolumeButton::volumeChanged, this, &MainToolBar::onVolumeChanged); addSeparator(); // --- Shuffle --- m_shuffle = addAction(Icon::get(QStringLiteral("media-playlist-shuffle")), tr("Shuffle")); m_shuffle->setCheckable(true); connect(m_shuffle, &QAction::toggled, this, &MainToolBar::onShuffleToggled); addSeparator(); // --- Queue toggle --- m_queueBtn = addAction(Icon::queue(), tr("Queue")); m_queueBtn->setCheckable(true); connect(m_queueBtn, &QAction::toggled, this, &MainToolBar::queueToggled); // --- Search toggle --- m_search = addAction(Icon::search(), tr("Search")); m_search->setCheckable(true); connect(m_search, &QAction::toggled, this, &MainToolBar::searchToggled); // --- Backend signals --- connect(m_backend, &QobuzBackend::stateChanged, this, &MainToolBar::onBackendStateChanged); connect(m_backend, &QobuzBackend::trackChanged, this, &MainToolBar::onTrackChanged); connect(m_backend, &QobuzBackend::positionChanged, this, &MainToolBar::onPositionChanged); connect(m_backend, &QobuzBackend::trackFinished, this, &MainToolBar::onTrackFinished); // --- Queue signals --- connect(m_queue, &PlayQueue::queueChanged, this, &MainToolBar::onQueueChanged); onQueueChanged(); // initialise button states } // ---- public ---- void MainToolBar::setPlaying(bool playing) { m_playing = playing; m_playPause->setIcon(playing ? Icon::pause() : Icon::play()); m_playPause->setText(playing ? tr("Pause") : tr("Play")); } void MainToolBar::setCurrentTrack(const QJsonObject &track) { const QString title = track["title"].toString(); const QString artist = track["performer"].toObject()["name"].toString().isEmpty() ? track["album"].toObject()["artist"].toObject()["name"].toString() : track["performer"].toObject()["name"].toString(); if (title.isEmpty()) { m_trackLabel->setText(tr("Not playing")); } else if (artist.isEmpty()) { m_trackLabel->setText(title); } else { m_trackLabel->setText(QStringLiteral("%1 — %2").arg(artist, title)); } // Album art const QString artUrl = track["album"].toObject()["image"].toObject()["small"].toString(); if (!artUrl.isEmpty() && artUrl != m_currentArtUrl) { m_currentArtUrl = artUrl; fetchAlbumArt(artUrl); } } void MainToolBar::updateProgress(quint64 position, quint64 duration) { if (m_seeking) return; const int sliderPos = duration > 0 ? static_cast(position * 1000 / duration) : 0; m_progress->blockSignals(true); m_progress->setValue(sliderPos); m_progress->blockSignals(false); m_timeLabel->setText( QStringLiteral("%1 / %2") .arg(TrackListModel::formatDuration(static_cast(position)), TrackListModel::formatDuration(static_cast(duration)))); } // ---- private slots ---- void MainToolBar::onPlayPause() { if (m_playing) m_backend->pause(); else m_backend->resume(); } void MainToolBar::onPrevious() { if (!m_queue->canGoPrev()) return; const QJsonObject track = m_queue->stepBack(); const qint64 id = static_cast(track["id"].toDouble()); if (id > 0) m_backend->playTrack(id, AppSettings::instance().preferredFormat()); } void MainToolBar::onNext() { if (!m_queue->canGoNext()) return; const QJsonObject track = m_queue->advance(); const qint64 id = static_cast(track["id"].toDouble()); if (id > 0) m_backend->playTrack(id, AppSettings::instance().preferredFormat()); } void MainToolBar::onProgressReleased() { m_seeking = false; } void MainToolBar::onVolumeChanged(int volume) { m_backend->setVolume(volume); AppSettings::instance().setVolume(volume); } void MainToolBar::onBackendStateChanged(const QString &state) { setPlaying(state == QStringLiteral("playing")); } void MainToolBar::onTrackChanged(const QJsonObject &track) { setCurrentTrack(track); } void MainToolBar::onPositionChanged(quint64 position, quint64 duration) { updateProgress(position, duration); } void MainToolBar::onTrackFinished() { // Auto-advance queue if (m_queue->canGoNext()) { onNext(); } else { setPlaying(false); m_progress->setValue(0); m_timeLabel->setText("0:00 / 0:00"); } } void MainToolBar::onQueueChanged() { m_previous->setEnabled(m_queue->canGoPrev()); m_next->setEnabled(m_queue->canGoNext()); } void MainToolBar::onShuffleToggled(bool checked) { m_queue->setShuffle(checked); } void MainToolBar::fetchAlbumArt(const QString &url) { m_nam->get(QNetworkRequest(QUrl(url))); } void MainToolBar::onAlbumArtReady(QNetworkReply *reply) { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) return; QPixmap pix; if (pix.loadFromData(reply->readAll())) m_artLabel->setPixmap(pix); }