refactor: UI polish — lock sidebar, remove nav buttons, uniform artist tables, deep shuffle
- Lock sidebar width (setFixedWidth) so it doesn't jump between views - Remove back/forward navigation buttons and all NavPage history code - Uniform column layout on artist page: hide Artist column from both Popular Tracks and release sections, set matching fixed column widths so columns align vertically across all sections - Deep shuffle: new Rust FFI endpoint fetches tracks from all albums in parallel, combines them, and returns via EV_DEEP_SHUFFLE_OK - Auto-paginate artist releases in Rust (loop until has_more=false) so all releases load at once sorted newest-first Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@ enum QobuzEvent {
|
|||||||
EV_PLAYLIST_DELETED = 21,
|
EV_PLAYLIST_DELETED = 21,
|
||||||
EV_PLAYLIST_TRACK_ADDED = 22,
|
EV_PLAYLIST_TRACK_ADDED = 22,
|
||||||
EV_USER_OK = 23,
|
EV_USER_OK = 23,
|
||||||
|
EV_ARTIST_RELEASES_OK = 24,
|
||||||
|
EV_DEEP_SHUFFLE_OK = 25,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Callback signature
|
// Callback signature
|
||||||
@@ -84,9 +86,12 @@ uint32_t qobuz_backend_viz_read(QobuzBackendOpaque *backend, float *buf, uint32_
|
|||||||
uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend);
|
uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend);
|
||||||
uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend);
|
uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend);
|
||||||
|
|
||||||
// Artist releases (full paginated list per release type)
|
// Artist releases (auto-paginates to fetch all)
|
||||||
void qobuz_backend_get_artist_releases(QobuzBackendOpaque *backend, int64_t artist_id, const char *release_type, uint32_t limit, uint32_t offset);
|
void qobuz_backend_get_artist_releases(QobuzBackendOpaque *backend, int64_t artist_id, const char *release_type, uint32_t limit, uint32_t offset);
|
||||||
|
|
||||||
|
// Deep shuffle: fetch tracks from multiple albums (album_ids_json is a JSON array of strings)
|
||||||
|
void qobuz_backend_get_albums_tracks(QobuzBackendOpaque *backend, const char *album_ids_json);
|
||||||
|
|
||||||
// Playlist management
|
// Playlist management
|
||||||
void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name);
|
void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name);
|
||||||
void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id);
|
void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id);
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub const EV_TRACK_URL_OK: c_int = 17;
|
|||||||
pub const EV_TRACK_URL_ERR: c_int = 18;
|
pub const EV_TRACK_URL_ERR: c_int = 18;
|
||||||
pub const EV_GENERIC_ERR: c_int = 19;
|
pub const EV_GENERIC_ERR: c_int = 19;
|
||||||
pub const EV_ARTIST_RELEASES_OK: c_int = 24;
|
pub const EV_ARTIST_RELEASES_OK: c_int = 24;
|
||||||
|
pub const EV_DEEP_SHUFFLE_OK: c_int = 25;
|
||||||
|
|
||||||
// ---------- Callback ----------
|
// ---------- Callback ----------
|
||||||
|
|
||||||
@@ -263,7 +264,7 @@ pub unsafe extern "C" fn qobuz_backend_get_artist_releases(
|
|||||||
artist_id: i64,
|
artist_id: i64,
|
||||||
release_type: *const c_char,
|
release_type: *const c_char,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
offset: u32,
|
_offset: u32,
|
||||||
) {
|
) {
|
||||||
let inner = &(*ptr).0;
|
let inner = &(*ptr).0;
|
||||||
let client = inner.client.clone();
|
let client = inner.client.clone();
|
||||||
@@ -271,19 +272,90 @@ pub unsafe extern "C" fn qobuz_backend_get_artist_releases(
|
|||||||
let rtype = CStr::from_ptr(release_type).to_string_lossy().into_owned();
|
let rtype = CStr::from_ptr(release_type).to_string_lossy().into_owned();
|
||||||
|
|
||||||
spawn(inner, async move {
|
spawn(inner, async move {
|
||||||
|
// Auto-paginate: fetch all pages until has_more is false.
|
||||||
|
let mut all_items: Vec<serde_json::Value> = Vec::new();
|
||||||
|
let mut offset: u32 = 0;
|
||||||
|
loop {
|
||||||
let result = client.lock().await
|
let result = client.lock().await
|
||||||
.get_artist_releases_list(artist_id, &rtype, limit, offset)
|
.get_artist_releases_list(artist_id, &rtype, limit, offset)
|
||||||
.await;
|
.await;
|
||||||
let (ev, json) = match result {
|
match result {
|
||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
// Wrap with the release_type so Qt can route to the right section
|
let obj = r.as_object().cloned().unwrap_or_default();
|
||||||
let mut obj = r.as_object().cloned().unwrap_or_default();
|
if let Some(items) = obj.get("items").and_then(|v| v.as_array()) {
|
||||||
obj.insert("release_type".to_string(), serde_json::Value::String(rtype));
|
all_items.extend(items.iter().cloned());
|
||||||
(EV_ARTIST_RELEASES_OK, serde_json::to_string(&obj).unwrap_or_default())
|
}
|
||||||
|
let has_more = obj.get("has_more").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||||
|
if !has_more {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset += limit;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = serde_json::json!({
|
||||||
|
"release_type": rtype,
|
||||||
|
"items": all_items,
|
||||||
|
"has_more": false,
|
||||||
|
"offset": 0
|
||||||
|
});
|
||||||
|
call_cb(cb, ud, EV_ARTIST_RELEASES_OK, &serde_json::to_string(&result).unwrap_or_default());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Deep shuffle (fetch tracks from multiple albums) ----------
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn qobuz_backend_get_albums_tracks(
|
||||||
|
ptr: *mut Backend,
|
||||||
|
album_ids_json: *const c_char,
|
||||||
|
) {
|
||||||
|
let inner = &(*ptr).0;
|
||||||
|
let client = inner.client.clone();
|
||||||
|
let cb = inner.cb; let ud = inner.ud;
|
||||||
|
let ids_str = CStr::from_ptr(album_ids_json).to_string_lossy().into_owned();
|
||||||
|
|
||||||
|
let album_ids: Vec<String> = match serde_json::from_str(&ids_str) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string()));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Err(e) => (EV_GENERIC_ERR, err_json(&e.to_string())),
|
|
||||||
};
|
};
|
||||||
call_cb(cb, ud, ev, &json);
|
|
||||||
|
spawn(inner, async move {
|
||||||
|
let mut all_tracks: Vec<serde_json::Value> = Vec::new();
|
||||||
|
for id in &album_ids {
|
||||||
|
let result = client.lock().await.get_album(id).await;
|
||||||
|
if let Ok(album) = result {
|
||||||
|
if let Some(tracks) = album.tracks.as_ref().and_then(|t| t.items.as_ref()) {
|
||||||
|
for t in tracks {
|
||||||
|
// Serialize track and inject album info for playback context
|
||||||
|
if let Ok(mut tv) = serde_json::to_value(t) {
|
||||||
|
if let Some(obj) = tv.as_object_mut() {
|
||||||
|
// Ensure album context is present on each track
|
||||||
|
if obj.get("album").is_none() || obj["album"].is_null() {
|
||||||
|
obj.insert("album".to_string(), serde_json::json!({
|
||||||
|
"id": album.id,
|
||||||
|
"title": album.title,
|
||||||
|
"artist": album.artist,
|
||||||
|
"image": album.image,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
all_tracks.push(tv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip albums that fail — don't abort the whole operation
|
||||||
|
}
|
||||||
|
let result = serde_json::json!({ "tracks": all_tracks });
|
||||||
|
call_cb(cb, ud, EV_DEEP_SHUFFLE_OK, &serde_json::to_string(&result).unwrap_or_default());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ void QobuzBackend::getArtistReleases(qint64 artistId, const QString &releaseType
|
|||||||
releaseType.toUtf8().constData(), limit, offset);
|
releaseType.toUtf8().constData(), limit, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QobuzBackend::getAlbumsTracks(const QStringList &albumIds)
|
||||||
|
{
|
||||||
|
const QJsonArray arr = QJsonArray::fromStringList(albumIds);
|
||||||
|
const QByteArray json = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||||
|
qobuz_backend_get_albums_tracks(m_backend, json.constData());
|
||||||
|
}
|
||||||
|
|
||||||
void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit)
|
void QobuzBackend::getPlaylist(qint64 playlistId, quint32 offset, quint32 limit)
|
||||||
{
|
{
|
||||||
qobuz_backend_get_playlist(m_backend, playlistId, offset, limit);
|
qobuz_backend_get_playlist(m_backend, playlistId, offset, limit);
|
||||||
@@ -266,6 +273,9 @@ void QobuzBackend::onEvent(int eventType, const QString &json)
|
|||||||
obj["offset"].toInt()
|
obj["offset"].toInt()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 25: // EV_DEEP_SHUFFLE_OK
|
||||||
|
emit deepShuffleTracksLoaded(obj["tracks"].toArray());
|
||||||
|
break;
|
||||||
case EV_ARTIST_ERR:
|
case EV_ARTIST_ERR:
|
||||||
emit error(obj["error"].toString());
|
emit error(obj["error"].toString());
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public:
|
|||||||
void getAlbum(const QString &albumId);
|
void getAlbum(const QString &albumId);
|
||||||
void getArtist(qint64 artistId);
|
void getArtist(qint64 artistId);
|
||||||
void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 50, quint32 offset = 0);
|
void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 50, quint32 offset = 0);
|
||||||
|
void getAlbumsTracks(const QStringList &albumIds);
|
||||||
void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500);
|
void getPlaylist(qint64 playlistId, quint32 offset = 0, quint32 limit = 500);
|
||||||
|
|
||||||
// --- favorites ---
|
// --- favorites ---
|
||||||
@@ -88,6 +89,7 @@ signals:
|
|||||||
void albumLoaded(const QJsonObject &album);
|
void albumLoaded(const QJsonObject &album);
|
||||||
void artistLoaded(const QJsonObject &artist);
|
void artistLoaded(const QJsonObject &artist);
|
||||||
void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);
|
void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);
|
||||||
|
void deepShuffleTracksLoaded(const QJsonArray &tracks);
|
||||||
void playlistLoaded(const QJsonObject &playlist);
|
void playlistLoaded(const QJsonObject &playlist);
|
||||||
void playlistCreated(const QJsonObject &playlist);
|
void playlistCreated(const QJsonObject &playlist);
|
||||||
void playlistDeleted(const QJsonObject &result);
|
void playlistDeleted(const QJsonObject &result);
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
|||||||
m_libraryDock->setObjectName(QStringLiteral("libraryDock"));
|
m_libraryDock->setObjectName(QStringLiteral("libraryDock"));
|
||||||
m_libraryDock->setFeatures(QDockWidget::DockWidgetMovable);
|
m_libraryDock->setFeatures(QDockWidget::DockWidgetMovable);
|
||||||
m_libraryDock->setWidget(m_library);
|
m_libraryDock->setWidget(m_library);
|
||||||
m_libraryDock->setMinimumWidth(200);
|
m_libraryDock->setMinimumWidth(180);
|
||||||
|
m_library->setFixedWidth(220);
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, m_libraryDock);
|
addDockWidget(Qt::LeftDockWidgetArea, m_libraryDock);
|
||||||
|
|
||||||
// ---- Now-playing context dock (left, below library) ----
|
// ---- Now-playing context dock (left, below library) ----
|
||||||
@@ -86,6 +87,8 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
|||||||
connect(m_backend, &QobuzBackend::artistLoaded, this, &MainWindow::onArtistLoaded);
|
connect(m_backend, &QobuzBackend::artistLoaded, this, &MainWindow::onArtistLoaded);
|
||||||
connect(m_backend, &QobuzBackend::artistReleasesLoaded,
|
connect(m_backend, &QobuzBackend::artistReleasesLoaded,
|
||||||
m_content, &MainContent::updateArtistReleases);
|
m_content, &MainContent::updateArtistReleases);
|
||||||
|
connect(m_backend, &QobuzBackend::deepShuffleTracksLoaded,
|
||||||
|
m_content, &MainContent::onDeepShuffleTracks);
|
||||||
connect(m_backend, &QobuzBackend::playlistLoaded, this, &MainWindow::onPlaylistLoaded);
|
connect(m_backend, &QobuzBackend::playlistLoaded, this, &MainWindow::onPlaylistLoaded);
|
||||||
connect(m_backend, &QobuzBackend::playlistCreated, this, &MainWindow::onPlaylistCreated);
|
connect(m_backend, &QobuzBackend::playlistCreated, this, &MainWindow::onPlaylistCreated);
|
||||||
connect(m_backend, &QobuzBackend::playlistDeleted, this, [this](const QJsonObject &) {
|
connect(m_backend, &QobuzBackend::playlistDeleted, this, [this](const QJsonObject &) {
|
||||||
@@ -170,22 +173,6 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
|||||||
connect(m_toolBar, &MainToolBar::queueToggled,
|
connect(m_toolBar, &MainToolBar::queueToggled,
|
||||||
this, [this](bool v) { m_queuePanel->setVisible(v); });
|
this, [this](bool v) { m_queuePanel->setVisible(v); });
|
||||||
|
|
||||||
connect(m_toolBar->backAction(), &QAction::triggered, this, [this] {
|
|
||||||
if (m_navIndex <= 0) return;
|
|
||||||
--m_navIndex;
|
|
||||||
m_navFromHistory = true;
|
|
||||||
navigateTo(m_navHistory[m_navIndex]);
|
|
||||||
m_navFromHistory = false;
|
|
||||||
updateNavButtons();
|
|
||||||
});
|
|
||||||
connect(m_toolBar->forwardAction(), &QAction::triggered, this, [this] {
|
|
||||||
if (m_navIndex >= m_navHistory.size() - 1) return;
|
|
||||||
++m_navIndex;
|
|
||||||
m_navFromHistory = true;
|
|
||||||
navigateTo(m_navHistory[m_navIndex]);
|
|
||||||
m_navFromHistory = false;
|
|
||||||
updateNavButtons();
|
|
||||||
});
|
|
||||||
connect(m_toolBar, &MainToolBar::albumRequested, this, &MainWindow::onSearchAlbumSelected);
|
connect(m_toolBar, &MainToolBar::albumRequested, this, &MainWindow::onSearchAlbumSelected);
|
||||||
connect(m_toolBar, &MainToolBar::artistRequested, this, &MainWindow::onSearchArtistSelected);
|
connect(m_toolBar, &MainToolBar::artistRequested, this, &MainWindow::onSearchArtistSelected);
|
||||||
|
|
||||||
@@ -394,16 +381,12 @@ void MainWindow::onPlayTrackRequested(qint64 trackId)
|
|||||||
|
|
||||||
void MainWindow::onSearchAlbumSelected(const QString &albumId)
|
void MainWindow::onSearchAlbumSelected(const QString &albumId)
|
||||||
{
|
{
|
||||||
NavPage p; p.type = NavPage::Album; p.albumId = albumId;
|
|
||||||
pushNav(p.type, p.albumId);
|
|
||||||
m_backend->getAlbum(albumId);
|
m_backend->getAlbum(albumId);
|
||||||
statusBar()->showMessage(tr("Loading album…"));
|
statusBar()->showMessage(tr("Loading album…"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onSearchArtistSelected(qint64 artistId)
|
void MainWindow::onSearchArtistSelected(qint64 artistId)
|
||||||
{
|
{
|
||||||
NavPage p; p.type = NavPage::Artist; p.artistId = artistId;
|
|
||||||
pushNav(p.type, {}, p.artistId);
|
|
||||||
m_backend->getArtist(artistId);
|
m_backend->getArtist(artistId);
|
||||||
statusBar()->showMessage(tr("Loading artist…"));
|
statusBar()->showMessage(tr("Loading artist…"));
|
||||||
}
|
}
|
||||||
@@ -429,38 +412,3 @@ void MainWindow::onUserPlaylistsChanged(const QVector<QPair<qint64, QString>> &p
|
|||||||
m_content->tracksList()->setUserPlaylists(playlists);
|
m_content->tracksList()->setUserPlaylists(playlists);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::pushNav(NavPage::Type type, const QString &albumId, qint64 artistId)
|
|
||||||
{
|
|
||||||
if (m_navFromHistory) return;
|
|
||||||
// Truncate any forward history
|
|
||||||
while (m_navHistory.size() > m_navIndex + 1)
|
|
||||||
m_navHistory.removeLast();
|
|
||||||
NavPage p;
|
|
||||||
p.type = type;
|
|
||||||
p.albumId = albumId;
|
|
||||||
p.artistId = artistId;
|
|
||||||
m_navHistory.push_back(p);
|
|
||||||
m_navIndex = m_navHistory.size() - 1;
|
|
||||||
updateNavButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::navigateTo(const NavPage &page)
|
|
||||||
{
|
|
||||||
switch (page.type) {
|
|
||||||
case NavPage::Album:
|
|
||||||
m_backend->getAlbum(page.albumId);
|
|
||||||
statusBar()->showMessage(tr("Loading album…"));
|
|
||||||
break;
|
|
||||||
case NavPage::Artist:
|
|
||||||
m_backend->getArtist(page.artistId);
|
|
||||||
statusBar()->showMessage(tr("Loading artist…"));
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::updateNavButtons()
|
|
||||||
{
|
|
||||||
m_toolBar->backAction()->setEnabled(m_navIndex > 0);
|
|
||||||
m_toolBar->forwardAction()->setEnabled(m_navIndex < m_navHistory.size() - 1);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -64,19 +64,6 @@ private:
|
|||||||
QDockWidget *m_libraryDock = nullptr;
|
QDockWidget *m_libraryDock = nullptr;
|
||||||
LastFmScrobbler *m_scrobbler = nullptr;
|
LastFmScrobbler *m_scrobbler = nullptr;
|
||||||
|
|
||||||
// Navigation history (browser-style Back / Forward)
|
|
||||||
struct NavPage {
|
|
||||||
enum Type { None, Album, Artist } type = None;
|
|
||||||
QString albumId;
|
|
||||||
qint64 artistId = 0;
|
|
||||||
};
|
|
||||||
QVector<NavPage> m_navHistory;
|
|
||||||
int m_navIndex = -1;
|
|
||||||
bool m_navFromHistory = false;
|
|
||||||
|
|
||||||
void setupMenuBar();
|
void setupMenuBar();
|
||||||
void tryRestoreSession();
|
void tryRestoreSession();
|
||||||
void pushNav(NavPage::Type type, const QString &albumId = {}, qint64 artistId = 0);
|
|
||||||
void navigateTo(const NavPage &page);
|
|
||||||
void updateNavButtons();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,20 @@ public:
|
|||||||
addAlbums(albums);
|
addAlbums(albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configure for artist page: hide Artist column, set fixed column widths
|
||||||
|
/// that match the Popular Tracks list for perfect vertical alignment.
|
||||||
|
void setArtistPageMode()
|
||||||
|
{
|
||||||
|
setColumnHidden(2, true); // Artist — redundant on artist page
|
||||||
|
header()->setSectionResizeMode(0, QHeaderView::Fixed);
|
||||||
|
header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||||
|
header()->setSectionResizeMode(3, QHeaderView::Fixed);
|
||||||
|
header()->setSectionResizeMode(4, QHeaderView::Fixed);
|
||||||
|
header()->resizeSection(0, 40);
|
||||||
|
header()->resizeSection(3, 120);
|
||||||
|
header()->resizeSection(4, 70);
|
||||||
|
}
|
||||||
|
|
||||||
void addAlbums(const QJsonArray &albums)
|
void addAlbums(const QJsonArray &albums)
|
||||||
{
|
{
|
||||||
QFont hiResFont;
|
QFont hiResFont;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#include "artistview.hpp"
|
#include "artistview.hpp"
|
||||||
#include "albumlistview.hpp"
|
#include "albumlistview.hpp"
|
||||||
|
#include "../model/tracklistmodel.hpp"
|
||||||
|
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
|
#include <QHeaderView>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
@@ -49,50 +51,18 @@ ArtistSection::ArtistSection(const QString &title, const QString &releaseType, Q
|
|||||||
m_list = new AlbumListView(this);
|
m_list = new AlbumListView(this);
|
||||||
layout->addWidget(m_list);
|
layout->addWidget(m_list);
|
||||||
|
|
||||||
// "Load more" button (shown when has_more is true)
|
|
||||||
m_loadMoreBtn = new QPushButton(tr("Load more…"), this);
|
|
||||||
m_loadMoreBtn->setStyleSheet(QStringLiteral(
|
|
||||||
"QPushButton { text-align: left; color: #FFB232; background: transparent;"
|
|
||||||
" border: none; padding: 6px 8px; }"
|
|
||||||
"QPushButton:hover { background: #1e1e1e; }"));
|
|
||||||
m_loadMoreBtn->setCursor(Qt::PointingHandCursor);
|
|
||||||
m_loadMoreBtn->setVisible(false);
|
|
||||||
layout->addWidget(m_loadMoreBtn);
|
|
||||||
|
|
||||||
connect(m_toggle, &QPushButton::toggled, this, [this](bool checked) {
|
connect(m_toggle, &QPushButton::toggled, this, [this](bool checked) {
|
||||||
m_list->setVisible(checked);
|
m_list->setVisible(checked);
|
||||||
m_loadMoreBtn->setVisible(checked && m_hasMore);
|
|
||||||
updateToggleText();
|
updateToggleText();
|
||||||
});
|
});
|
||||||
connect(m_list, &AlbumListView::albumSelected, this, &ArtistSection::albumSelected);
|
connect(m_list, &AlbumListView::albumSelected, this, &ArtistSection::albumSelected);
|
||||||
connect(m_loadMoreBtn, &QPushButton::clicked, this, [this] {
|
|
||||||
m_loadMoreBtn->setEnabled(false);
|
|
||||||
m_loadMoreBtn->setText(tr("Loading…"));
|
|
||||||
emit loadMoreRequested(m_releaseType, m_loadedCount);
|
|
||||||
});
|
|
||||||
|
|
||||||
updateToggleText();
|
updateToggleText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArtistSection::setAlbums(const QJsonArray &albums, bool hasMore)
|
void ArtistSection::setAlbums(const QJsonArray &albums)
|
||||||
{
|
{
|
||||||
m_list->setAlbums(albums);
|
m_list->setAlbums(albums);
|
||||||
m_loadedCount = albums.size();
|
|
||||||
m_hasMore = hasMore;
|
|
||||||
m_loadMoreBtn->setVisible(hasMore && m_toggle->isChecked());
|
|
||||||
m_loadMoreBtn->setEnabled(true);
|
|
||||||
m_loadMoreBtn->setText(tr("Load more…"));
|
|
||||||
updateToggleText();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArtistSection::appendAlbums(const QJsonArray &albums, bool hasMore)
|
|
||||||
{
|
|
||||||
m_list->addAlbums(albums);
|
|
||||||
m_loadedCount += albums.size();
|
|
||||||
m_hasMore = hasMore;
|
|
||||||
m_loadMoreBtn->setVisible(hasMore && m_toggle->isChecked());
|
|
||||||
m_loadMoreBtn->setEnabled(true);
|
|
||||||
m_loadMoreBtn->setText(tr("Load more…"));
|
|
||||||
updateToggleText();
|
updateToggleText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +71,28 @@ bool ArtistSection::isEmpty() const
|
|||||||
return m_list->topLevelItemCount() == 0;
|
return m_list->topLevelItemCount() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList ArtistSection::albumIds() const
|
||||||
|
{
|
||||||
|
QStringList ids;
|
||||||
|
for (int i = 0; i < m_list->topLevelItemCount(); ++i) {
|
||||||
|
const QString id = m_list->topLevelItem(i)->data(1, Qt::UserRole).toString();
|
||||||
|
if (!id.isEmpty())
|
||||||
|
ids.append(id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArtistSection::setArtistPageMode()
|
||||||
|
{
|
||||||
|
m_list->setArtistPageMode();
|
||||||
|
}
|
||||||
|
|
||||||
void ArtistSection::updateToggleText()
|
void ArtistSection::updateToggleText()
|
||||||
{
|
{
|
||||||
|
const int count = m_list->topLevelItemCount();
|
||||||
const QString arrow = m_toggle->isChecked() ? QStringLiteral("▼ ") : QStringLiteral("▶ ");
|
const QString arrow = m_toggle->isChecked() ? QStringLiteral("▼ ") : QStringLiteral("▶ ");
|
||||||
const QString text = m_loadedCount > 0
|
const QString text = count > 0
|
||||||
? QStringLiteral("%1%2 (%3)").arg(arrow, m_baseTitle).arg(m_loadedCount)
|
? QStringLiteral("%1%2 (%3)").arg(arrow, m_baseTitle).arg(count)
|
||||||
: arrow + m_baseTitle;
|
: arrow + m_baseTitle;
|
||||||
m_toggle->setText(text);
|
m_toggle->setText(text);
|
||||||
}
|
}
|
||||||
@@ -117,6 +104,7 @@ void ArtistSection::updateToggleText()
|
|||||||
ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_backend(backend)
|
, m_backend(backend)
|
||||||
|
, m_queue(queue)
|
||||||
{
|
{
|
||||||
auto *outerLayout = new QVBoxLayout(this);
|
auto *outerLayout = new QVBoxLayout(this);
|
||||||
outerLayout->setContentsMargins(0, 0, 0, 0);
|
outerLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
@@ -165,7 +153,7 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
|||||||
QStringLiteral("QPushButton { background: #FFB232; color: #000; }"
|
QStringLiteral("QPushButton { background: #FFB232; color: #000; }"
|
||||||
"QPushButton:pressed { background: #e09e28; }"));
|
"QPushButton:pressed { background: #e09e28; }"));
|
||||||
|
|
||||||
m_shuffleBtn = new QPushButton(tr("⇄ Shuffle"), info);
|
m_shuffleBtn = new QPushButton(tr("⇄ Shuffle All"), info);
|
||||||
m_shuffleBtn->setStyleSheet(kBtnBase +
|
m_shuffleBtn->setStyleSheet(kBtnBase +
|
||||||
QStringLiteral("QPushButton { background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
|
QStringLiteral("QPushButton { background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
|
||||||
"QPushButton:pressed { background: #333; }"));
|
"QPushButton:pressed { background: #333; }"));
|
||||||
@@ -222,6 +210,14 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
|||||||
|
|
||||||
m_topTracks = new List::Tracks(backend, queue, m_topTracksSection);
|
m_topTracks = new List::Tracks(backend, queue, m_topTracksSection);
|
||||||
m_topTracks->setMaximumHeight(320);
|
m_topTracks->setMaximumHeight(320);
|
||||||
|
// Artist page column layout: hide Artist & Album, match album-section widths
|
||||||
|
m_topTracks->setColumnHidden(TrackListModel::ColArtist, true);
|
||||||
|
m_topTracks->setColumnHidden(TrackListModel::ColAlbum, true);
|
||||||
|
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColNumber, QHeaderView::Fixed);
|
||||||
|
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColTitle, QHeaderView::Stretch);
|
||||||
|
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColDuration, QHeaderView::Fixed);
|
||||||
|
m_topTracks->header()->resizeSection(TrackListModel::ColNumber, 40);
|
||||||
|
m_topTracks->header()->resizeSection(TrackListModel::ColDuration, 70);
|
||||||
ttLayout->addWidget(m_topTracks);
|
ttLayout->addWidget(m_topTracks);
|
||||||
|
|
||||||
connect(m_topTracksToggle, &QPushButton::toggled, m_topTracks, &QWidget::setVisible);
|
connect(m_topTracksToggle, &QPushButton::toggled, m_topTracks, &QWidget::setVisible);
|
||||||
@@ -236,6 +232,10 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
|||||||
m_secCompilations = new ArtistSection(tr("Compilations"), QStringLiteral("compilation"), content);
|
m_secCompilations = new ArtistSection(tr("Compilations"), QStringLiteral("compilation"), content);
|
||||||
m_secOther = new ArtistSection(tr("Other"), QStringLiteral("other"), content);
|
m_secOther = new ArtistSection(tr("Other"), QStringLiteral("other"), content);
|
||||||
|
|
||||||
|
// Uniform column layout: hide Artist column, match fixed widths across all sections
|
||||||
|
for (ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther})
|
||||||
|
sec->setArtistPageMode();
|
||||||
|
|
||||||
sectLayout->addWidget(m_secAlbums);
|
sectLayout->addWidget(m_secAlbums);
|
||||||
sectLayout->addWidget(m_secEps);
|
sectLayout->addWidget(m_secEps);
|
||||||
sectLayout->addWidget(m_secLive);
|
sectLayout->addWidget(m_secLive);
|
||||||
@@ -246,9 +246,21 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
|||||||
scroll->setWidget(content);
|
scroll->setWidget(content);
|
||||||
outerLayout->addWidget(scroll, 1);
|
outerLayout->addWidget(scroll, 1);
|
||||||
|
|
||||||
// Playback connections
|
// Play top tracks
|
||||||
connect(m_playBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(false); });
|
connect(m_playBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(false); });
|
||||||
connect(m_shuffleBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(true); });
|
|
||||||
|
// Deep shuffle: fetch all album tracks, combine, shuffle, play
|
||||||
|
connect(m_shuffleBtn, &QPushButton::clicked, this, [this] {
|
||||||
|
const QStringList ids = allAlbumIds();
|
||||||
|
if (ids.isEmpty()) {
|
||||||
|
// Fallback: just shuffle popular tracks
|
||||||
|
m_topTracks->playAll(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_shuffleBtn->setEnabled(false);
|
||||||
|
m_shuffleBtn->setText(tr("Loading…"));
|
||||||
|
m_backend->getAlbumsTracks(ids);
|
||||||
|
});
|
||||||
|
|
||||||
// Favourite button
|
// Favourite button
|
||||||
connect(m_favBtn, &QPushButton::clicked, this, [this] {
|
connect(m_favBtn, &QPushButton::clicked, this, [this] {
|
||||||
@@ -270,20 +282,6 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
|||||||
connect(m_secLive, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
connect(m_secLive, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||||
connect(m_secCompilations, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
connect(m_secCompilations, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||||
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||||
|
|
||||||
// Load-more connections
|
|
||||||
auto connectLoadMore = [this](ArtistSection *sec) {
|
|
||||||
connect(sec, &ArtistSection::loadMoreRequested, this,
|
|
||||||
[this](const QString &releaseType, int nextOffset) {
|
|
||||||
if (m_artistId > 0)
|
|
||||||
m_backend->getArtistReleases(m_artistId, releaseType, 50, static_cast<quint32>(nextOffset));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
connectLoadMore(m_secAlbums);
|
|
||||||
connectLoadMore(m_secEps);
|
|
||||||
connectLoadMore(m_secLive);
|
|
||||||
connectLoadMore(m_secCompilations);
|
|
||||||
connectLoadMore(m_secOther);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArtistView::setArtist(const QJsonObject &artist)
|
void ArtistView::setArtist(const QJsonObject &artist)
|
||||||
@@ -352,6 +350,10 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
|||||||
: QStringLiteral("▼ Popular Tracks"));
|
: QStringLiteral("▼ Popular Tracks"));
|
||||||
m_topTracksSection->setVisible(!topTracks.isEmpty());
|
m_topTracksSection->setVisible(!topTracks.isEmpty());
|
||||||
|
|
||||||
|
// Reset shuffle button state
|
||||||
|
m_shuffleBtn->setEnabled(true);
|
||||||
|
m_shuffleBtn->setText(tr("⇄ Shuffle All"));
|
||||||
|
|
||||||
// Clear release sections
|
// Clear release sections
|
||||||
for (ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther}) {
|
for (ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther}) {
|
||||||
sec->setAlbums({});
|
sec->setAlbums({});
|
||||||
@@ -360,7 +362,7 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items,
|
void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items,
|
||||||
bool hasMore, int offset)
|
bool /*hasMore*/, int /*offset*/)
|
||||||
{
|
{
|
||||||
ArtistSection *sec = nullptr;
|
ArtistSection *sec = nullptr;
|
||||||
if (releaseType == QStringLiteral("album")) sec = m_secAlbums;
|
if (releaseType == QStringLiteral("album")) sec = m_secAlbums;
|
||||||
@@ -369,22 +371,42 @@ void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items
|
|||||||
else if (releaseType == QStringLiteral("compilation")) sec = m_secCompilations;
|
else if (releaseType == QStringLiteral("compilation")) sec = m_secCompilations;
|
||||||
else sec = m_secOther;
|
else sec = m_secOther;
|
||||||
|
|
||||||
if (offset == 0)
|
// Rust auto-paginates, so we always get the full list at once
|
||||||
sec->setAlbums(items, hasMore);
|
sec->setAlbums(items);
|
||||||
else
|
|
||||||
sec->appendAlbums(items, hasMore);
|
|
||||||
|
|
||||||
sec->setVisible(!sec->isEmpty());
|
sec->setVisible(!sec->isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArtistView::setFavArtistIds(const QSet<qint64> &ids)
|
void ArtistView::setFavArtistIds(const QSet<qint64> &ids)
|
||||||
{
|
{
|
||||||
m_favArtistIds = ids;
|
m_favArtistIds = ids;
|
||||||
// Update current state if we're showing an artist
|
|
||||||
if (m_artistId > 0)
|
if (m_artistId > 0)
|
||||||
setFaved(ids.contains(m_artistId));
|
setFaved(ids.contains(m_artistId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ArtistView::onDeepShuffleTracks(const QJsonArray &tracks)
|
||||||
|
{
|
||||||
|
m_shuffleBtn->setEnabled(true);
|
||||||
|
m_shuffleBtn->setText(tr("⇄ Shuffle All"));
|
||||||
|
|
||||||
|
if (tracks.isEmpty()) return;
|
||||||
|
|
||||||
|
m_queue->setContext(tracks, 0);
|
||||||
|
m_queue->shuffleNow();
|
||||||
|
|
||||||
|
const QJsonObject first = m_queue->current();
|
||||||
|
const qint64 id = static_cast<qint64>(first["id"].toDouble());
|
||||||
|
if (id > 0)
|
||||||
|
emit playTrackRequested(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ArtistView::allAlbumIds() const
|
||||||
|
{
|
||||||
|
QStringList ids;
|
||||||
|
for (const ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther})
|
||||||
|
ids.append(sec->albumIds());
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
void ArtistView::setFaved(bool faved)
|
void ArtistView::setFaved(bool faved)
|
||||||
{
|
{
|
||||||
m_isFaved = faved;
|
m_isFaved = faved;
|
||||||
|
|||||||
@@ -23,22 +23,19 @@ class ArtistSection : public QWidget
|
|||||||
public:
|
public:
|
||||||
explicit ArtistSection(const QString &title, const QString &releaseType, QWidget *parent = nullptr);
|
explicit ArtistSection(const QString &title, const QString &releaseType, QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setAlbums(const QJsonArray &albums, bool hasMore = false);
|
void setAlbums(const QJsonArray &albums);
|
||||||
void appendAlbums(const QJsonArray &albums, bool hasMore = false);
|
|
||||||
bool isEmpty() const;
|
bool isEmpty() const;
|
||||||
|
QStringList albumIds() const;
|
||||||
|
void setArtistPageMode();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumSelected(const QString &albumId);
|
void albumSelected(const QString &albumId);
|
||||||
void loadMoreRequested(const QString &releaseType, int nextOffset);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_baseTitle;
|
QString m_baseTitle;
|
||||||
QString m_releaseType;
|
QString m_releaseType;
|
||||||
QPushButton *m_toggle = nullptr;
|
QPushButton *m_toggle = nullptr;
|
||||||
AlbumListView *m_list = nullptr;
|
AlbumListView *m_list = nullptr;
|
||||||
QPushButton *m_loadMoreBtn = nullptr;
|
|
||||||
bool m_hasMore = false;
|
|
||||||
int m_loadedCount = 0;
|
|
||||||
|
|
||||||
void updateToggleText();
|
void updateToggleText();
|
||||||
};
|
};
|
||||||
@@ -55,6 +52,7 @@ public:
|
|||||||
void setReleases(const QString &releaseType, const QJsonArray &items,
|
void setReleases(const QString &releaseType, const QJsonArray &items,
|
||||||
bool hasMore = false, int offset = 0);
|
bool hasMore = false, int offset = 0);
|
||||||
void setFavArtistIds(const QSet<qint64> &ids);
|
void setFavArtistIds(const QSet<qint64> &ids);
|
||||||
|
void onDeepShuffleTracks(const QJsonArray &tracks);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumSelected(const QString &albumId);
|
void albumSelected(const QString &albumId);
|
||||||
@@ -62,6 +60,7 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
QobuzBackend *m_backend = nullptr;
|
QobuzBackend *m_backend = nullptr;
|
||||||
|
PlayQueue *m_queue = nullptr;
|
||||||
qint64 m_artistId = 0;
|
qint64 m_artistId = 0;
|
||||||
|
|
||||||
// Header widgets
|
// Header widgets
|
||||||
@@ -88,5 +87,6 @@ private:
|
|||||||
ArtistSection *m_secCompilations = nullptr;
|
ArtistSection *m_secCompilations = nullptr;
|
||||||
ArtistSection *m_secOther = nullptr;
|
ArtistSection *m_secOther = nullptr;
|
||||||
|
|
||||||
|
QStringList allAlbumIds() const;
|
||||||
void setFaved(bool faved);
|
void setFaved(bool faved);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,3 +117,8 @@ void MainContent::setFavArtistIds(const QSet<qint64> &ids)
|
|||||||
{
|
{
|
||||||
m_artistView->setFavArtistIds(ids);
|
m_artistView->setFavArtistIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainContent::onDeepShuffleTracks(const QJsonArray &tracks)
|
||||||
|
{
|
||||||
|
m_artistView->onDeepShuffleTracks(tracks);
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ public:
|
|||||||
void showArtist(const QJsonObject &artist);
|
void showArtist(const QJsonObject &artist);
|
||||||
void updateArtistReleases(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);
|
void updateArtistReleases(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);
|
||||||
void setFavArtistIds(const QSet<qint64> &ids);
|
void setFavArtistIds(const QSet<qint64> &ids);
|
||||||
|
void onDeepShuffleTracks(const QJsonArray &tracks);
|
||||||
|
|
||||||
|
ArtistView *artistView() const { return m_artistView; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void albumRequested(const QString &albumId);
|
void albumRequested(const QString &albumId);
|
||||||
|
|||||||
@@ -16,13 +16,6 @@ MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
|
|||||||
setContextMenuPolicy(Qt::PreventContextMenu);
|
setContextMenuPolicy(Qt::PreventContextMenu);
|
||||||
setIconSize(QSize(22, 22));
|
setIconSize(QSize(22, 22));
|
||||||
|
|
||||||
// ---- Back / Forward navigation ----
|
|
||||||
m_back = addAction(QIcon::fromTheme(QStringLiteral("go-previous")), tr("Back"));
|
|
||||||
m_back->setEnabled(false);
|
|
||||||
m_fwd = addAction(QIcon::fromTheme(QStringLiteral("go-next")), tr("Forward"));
|
|
||||||
m_fwd->setEnabled(false);
|
|
||||||
addSeparator();
|
|
||||||
|
|
||||||
m_nam = new QNetworkAccessManager(this);
|
m_nam = new QNetworkAccessManager(this);
|
||||||
connect(m_nam, &QNetworkAccessManager::finished, this, &MainToolBar::onAlbumArtReady);
|
connect(m_nam, &QNetworkAccessManager::finished, this, &MainToolBar::onAlbumArtReady);
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ public:
|
|||||||
void setCurrentTrack(const QJsonObject &track);
|
void setCurrentTrack(const QJsonObject &track);
|
||||||
void updateProgress(quint64 position, quint64 duration);
|
void updateProgress(quint64 position, quint64 duration);
|
||||||
|
|
||||||
QAction *backAction() const { return m_back; }
|
|
||||||
QAction *forwardAction() const { return m_fwd; }
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void searchToggled(bool visible);
|
void searchToggled(bool visible);
|
||||||
void queueToggled(bool visible);
|
void queueToggled(bool visible);
|
||||||
@@ -57,8 +54,6 @@ private:
|
|||||||
QobuzBackend *m_backend = nullptr;
|
QobuzBackend *m_backend = nullptr;
|
||||||
PlayQueue *m_queue = nullptr;
|
PlayQueue *m_queue = nullptr;
|
||||||
|
|
||||||
QAction *m_back = nullptr;
|
|
||||||
QAction *m_fwd = nullptr;
|
|
||||||
QLabel *m_artLabel = nullptr;
|
QLabel *m_artLabel = nullptr;
|
||||||
QLabel *m_trackLabel = nullptr;
|
QLabel *m_trackLabel = nullptr;
|
||||||
QAction *m_previous = nullptr;
|
QAction *m_previous = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user