From 12273256e5130f61e2ebf37c74cd2cfabae2fa1a Mon Sep 17 00:00:00 2001 From: David Sansome Date: Sun, 28 Feb 2010 18:04:50 +0000 Subject: [PATCH] Album cover art manager. So far this only displays cover art that was loaded from disk. --- data/data.qrc | 2 + data/download.png | Bin 0 -> 1462 bytes data/view-choose.png | Bin 0 -> 409 bytes src/CMakeLists.txt | 5 ++ src/albumcoverloader.cpp | 70 +++++++++++++++ src/albumcoverloader.h | 42 +++++++++ src/albumcovermanager.cpp | 160 ++++++++++++++++++++++++++++++++++ src/albumcovermanager.h | 59 +++++++++++++ src/albumcovermanager.ui | 175 ++++++++++++++++++++++++++++++++++++++ src/clementine_el.ts | 107 +++++++++++++++++++++++ src/clementine_es.ts | 107 +++++++++++++++++++++++ src/clementine_ru.ts | 107 +++++++++++++++++++++++ src/library.cpp | 8 +- src/library.h | 2 + src/librarybackend.cpp | 43 +++++++++- src/librarybackend.h | 24 ++++-- src/libraryquery.cpp | 3 + src/libraryquery.h | 2 + src/mainwindow.cpp | 5 ++ src/mainwindow.h | 2 + src/mainwindow.ui | 32 ++++++- src/src.pro | 11 ++- 22 files changed, 946 insertions(+), 20 deletions(-) create mode 100644 data/download.png create mode 100644 data/view-choose.png create mode 100644 src/albumcoverloader.cpp create mode 100644 src/albumcoverloader.h create mode 100644 src/albumcovermanager.cpp create mode 100644 src/albumcovermanager.h create mode 100644 src/albumcovermanager.ui diff --git a/data/data.qrc b/data/data.qrc index d82d2194b..22edaf759 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -62,5 +62,7 @@ schema-1.sql schema-2.sql nocover.png + view-choose.png + download.png diff --git a/data/download.png b/data/download.png new file mode 100644 index 0000000000000000000000000000000000000000..458890be14d1fecf8b30e6ff2bb37696c8b92d02 GIT binary patch literal 1462 zcmV;n1xfmeP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L011Qu011Qvs^VjL00007bV*G`2iXT6 z5-l~7IPxg~00lNlL_t(I%cay?Y*Td{2k`IzoYT|O)2{bzUB}j4+6{JLT!wB4Y=MAW zBDh4v>;*N!XiP|aG0|~HUrdNb5{*Hl7(-(8MGW2`fZGhg0aF;Tv1_+p)|IvE{hZVG z{C!vi2J#}`=U+bg<@@j@cp#;oBk&I&fUWFcgCb=*TUy=g3ygx3(XebdoC*vLO!X>R z^;E?FexK8G7=ht~cwESR^Z?=s7%djfsdcRt+n(!Kfm;JX1Vb?zk4Z$%sjwCqA!UJ{ zv)6kSnY=JPwlDNJn4L!tBA4Ub^|jW4*IrrOwB`AgbT%##uYV5xcVQ+|Kwa+%V0^ZPGNnUX#ljxrQPq@@`6nT{|u$kF`H{WQp`9cZQHkJ`aCqSWqy~u<_ zG6Q#w0|Y9DZU;zS&SAsWCU$VZ_bR7dd%|RGOD3Wh7r=~vct04(-@LT5*)};HgR7{0#>KR zu08E=Ry#n|{BHlq{)JVr3~3L~W$<(N2;yl4lpzoV4Qyr|e8C8yW?`_1U_=2_r-i|4 zKudEOT9?|Hj>hc(aQbTU9?WHxe5oMvmzYjIlb_C}n3`P{>C_q8M8g$4K8MPH=9R4T4| z2&a@DR^VF;L=GCBL7_#!*F9rssCA&LV+khbB%B)xVc1YU1FGU+S; zKom8QBI66;v`oJ@u=%%RF^O6&2688mKzl<893^H5Jclx?9=lr&(2^_)bu1Q{1<0`g zCcS=0(mbpacqnoIqg&yqJn*YnV%@|Qn*oafR#zd~TU^*w=R`cKLa$*E8uQ`9FM2TI zi$Y&u#*XGZlso|epy4P^UtuZDCWw2H4Gz10EnDh23V{F3z~%OE7NfxE1!M_>C?#0D z$cSQtfMSuw@XbL?j1FOOX%6M4G!p4NvUvuIq9UGM7AqXa<3@VE_s55| z)tz0}*`*%)!AFn(G()$({(%a8X+NGM+T!IPWi2dti@f*vIkoHLRTV^_8jHuMvACbe z24Rd~3QE@$)jdVao?cIh&bIyG8}9$WRE>is;=Sb!+g=98>8CEAKmG8{KS@6`$X^P7 Qvj6}907*qoM6N<$g2U988UO$Q literal 0 HcmV?d00001 diff --git a/data/view-choose.png b/data/view-choose.png new file mode 100644 index 0000000000000000000000000000000000000000..c0fa58ecf96cc4e09c0cea841f58e7f8e9f527b4 GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H3?x5i&EW)6jKx9jP7LeL$-D$|I14-?i-C%- zgD|6$#_S59AbW|YuPggaMpi~kH9dittw5oQ0G|+7AdLq8|NqYvdy+Zs6ie(u*2J5V zxmVP44bWotk|4ie z1||VrJ;Og)6DwnZLX1h??k?)O{411!oOVwa$B>F!XD8m|YcddUW%mjcaBbbRRcF)B z|Mf2dW-Mv^b?&6iojG@&*zq(ZGamT*gYi7;vwHp?JU?uF=kxmfO5>~1Ii1Hmd7t3d z1+$_uU!}S7uK4YxymU>6oax&q$CfSJyti)I$?Zy85}Sb4q9e08^o|r~m)} literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93eb15aef..f90b23417 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,8 @@ set(CLEMENTINE-SOURCES savedradio.cpp stylesheetloader.cpp shortcutsdialog.cpp + albumcovermanager.cpp + albumcoverloader.cpp ) # Header files that have Q_OBJECT in @@ -96,6 +98,8 @@ set(CLEMENTINE-MOC-HEADERS addstreamdialog.h savedradio.h shortcutsdialog.h + albumcovermanager.h + albumcoverloader.h ) # UI files @@ -114,6 +118,7 @@ set(CLEMENTINE-UI about.ui addstreamdialog.ui shortcutsdialog.ui + albumcovermanager.ui ) # Resource files diff --git a/src/albumcoverloader.cpp b/src/albumcoverloader.cpp new file mode 100644 index 000000000..9af885427 --- /dev/null +++ b/src/albumcoverloader.cpp @@ -0,0 +1,70 @@ +#include "albumcoverloader.h" + +#include + +AlbumCoverLoader::AlbumCoverLoader(QObject* parent) + : QObject(parent), + height_(120), + next_id_(0) +{ +} + +void AlbumCoverLoader::Clear() { + QMutexLocker l(&mutex_); + tasks_.clear(); +} + +quint64 AlbumCoverLoader::LoadImageAsync(const QString& art_automatic, + const QString& art_manual) { + Task task; + task.art_automatic = art_automatic; + task.art_manual = art_manual; + + { + QMutexLocker l(&mutex_); + task.id = next_id_ ++; + tasks_.enqueue(task); + } + + metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection); + + return task.id; +} + +void AlbumCoverLoader::ProcessTasks() { + forever { + // Get the next task + Task task; + { + QMutexLocker l(&mutex_); + if (tasks_.isEmpty()) + return; + task = tasks_.dequeue(); + } + + // Try to load the image + QImage image; + if (!task.art_manual.isEmpty()) + image.load(task.art_manual); + if (!task.art_automatic.isEmpty() && image.isNull()) + image.load(task.art_automatic); + + if (!image.isNull()) { + // Scale the image down + image = image.scaled(QSize(height_, height_), Qt::KeepAspectRatio, Qt::SmoothTransformation); + + // Pad the image to height_ x height_ + QImage bigger_image(height_, height_, QImage::Format_ARGB32); + bigger_image.fill(0); + + QPainter p(&bigger_image); + p.drawImage((height_ - image.width()) / 2, (height_ - image.height()) / 2, + image); + p.end(); + + image = bigger_image; + } + + emit ImageLoaded(task.id, image); + } +} diff --git a/src/albumcoverloader.h b/src/albumcoverloader.h new file mode 100644 index 000000000..9e1958d35 --- /dev/null +++ b/src/albumcoverloader.h @@ -0,0 +1,42 @@ +#ifndef ALBUMCOVERLOADER_H +#define ALBUMCOVERLOADER_H + +#include "backgroundthread.h" + +#include +#include +#include +#include + +class AlbumCoverLoader : public QObject { + Q_OBJECT + + public: + AlbumCoverLoader(QObject* parent = 0); + + void SetDesiredHeight(int height) { height_ = height; } + quint64 LoadImageAsync(const QString& art_automatic, const QString& art_manual); + + void Clear(); + + signals: + void ImageLoaded(quint64 id, const QImage& image); + + private slots: + void ProcessTasks(); + + private: + struct Task { + quint64 id; + QString art_automatic; + QString art_manual; + }; + + int height_; + + QMutex mutex_; + QQueue tasks_; + quint64 next_id_; +}; + +#endif // ALBUMCOVERLOADER_H diff --git a/src/albumcovermanager.cpp b/src/albumcovermanager.cpp new file mode 100644 index 000000000..e697df934 --- /dev/null +++ b/src/albumcovermanager.cpp @@ -0,0 +1,160 @@ +#include "albumcovermanager.h" +#include "librarybackend.h" +#include "libraryquery.h" + +#include +#include +#include +#include + +const char* AlbumCoverManager::kSettingsGroup = "CoverManager"; + +AlbumCoverManager::AlbumCoverManager(QWidget *parent) + : QDialog(parent), + cover_loader_(new BackgroundThread(this)), + artist_icon_(":/artist.png"), + all_artists_icon_(":/album.png") +{ + ui_.setupUi(this); + + // Get a square version of nocover.png + QImage nocover(":/nocover.png"); + nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QImage square_nocover(120, 120, QImage::Format_ARGB32); + square_nocover.fill(0); + QPainter p(&square_nocover); + p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover); + p.end(); + no_cover_icon_ = QPixmap::fromImage(square_nocover); + + // View menu + QActionGroup* filter_group = new QActionGroup(this); + filter_all_ = filter_group->addAction("All albums"); + filter_with_covers_ = filter_group->addAction("Albums with covers"); + filter_without_covers_ = filter_group->addAction("Albums without covers"); + filter_all_->setCheckable(true); + filter_with_covers_->setCheckable(true); + filter_without_covers_->setCheckable(true); + filter_group->setExclusive(true); + filter_all_->setChecked(true); + + QMenu* view_menu = new QMenu(this); + view_menu->addActions(filter_group->actions()); + + ui_.view->setMenu(view_menu); + + // Connections + connect(cover_loader_, SIGNAL(Initialised()), SLOT(CoverLoaderInitialised())); + connect(ui_.artists, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), + SLOT(ArtistChanged(QListWidgetItem*))); + connect(ui_.filter, SIGNAL(textChanged(QString)), SLOT(UpdateFilter())); + connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter())); + connect(ui_.view, SIGNAL(clicked()), ui_.view, SLOT(showMenu())); + + // Restore settings + QSettings s; + s.beginGroup(kSettingsGroup); + + restoreGeometry(s.value("geometry").toByteArray()); + if (!ui_.splitter->restoreState(s.value("splitter_state").toByteArray())) { + // Sensible default size for the artists view + ui_.splitter->setSizes(QList() << 200 << width() - 200); + } + + cover_loader_->start(); +} + +void AlbumCoverManager::CoverLoaderInitialised() { + connect(cover_loader_->Worker().get(), SIGNAL(ImageLoaded(quint64,QImage)), + SLOT(CoverImageLoaded(quint64,QImage))); +} + +void AlbumCoverManager::SetBackend(boost::shared_ptr backend) { + backend_ = backend; + + if (isVisible()) + Reset(); +} + +void AlbumCoverManager::showEvent(QShowEvent *) { + Reset(); +} + +void AlbumCoverManager::closeEvent(QCloseEvent *) { + QSettings s; + s.beginGroup(kSettingsGroup); + + s.setValue("geometry", saveGeometry()); + s.setValue("splitter_state", ui_.splitter->saveState()); +} + +void AlbumCoverManager::Reset() { + if (!backend_) + return; + + ui_.artists->clear(); + new QListWidgetItem(all_artists_icon_, "All artists", ui_.artists, All_Artists); + + foreach (const QString& artist, backend_->GetAllArtists()) { + if (artist.isEmpty()) + continue; + + new QListWidgetItem(artist_icon_, artist, ui_.artists, Specific_Artist); + } +} + +void AlbumCoverManager::ArtistChanged(QListWidgetItem* current) { + if (!backend_ || !cover_loader_->Worker()) + return; + if (!current) + return; + + QString artist; + if (current->type() == Specific_Artist) + artist = current->text(); + + ui_.albums->clear(); + cover_loading_tasks_.clear(); + cover_loader_->Worker()->Clear(); + + foreach (const LibraryBackend::AlbumArtInfo& info, backend_->GetAlbumArtInfo(artist)) { + QListWidgetItem* item = new QListWidgetItem(no_cover_icon_, info.album_name, ui_.albums); + + if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) { + quint64 id = cover_loader_->Worker()->LoadImageAsync( + info.art_automatic, info.art_manual); + cover_loading_tasks_[id] = item; + } + } + + UpdateFilter(); +} + +void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) { + if (!cover_loading_tasks_.contains(id)) + return; + + QListWidgetItem* item = cover_loading_tasks_.take(id); + + if (image.isNull()) + return; + + item->setIcon(QPixmap::fromImage(image)); + UpdateFilter(); +} + +void AlbumCoverManager::UpdateFilter() { + const QString filter = ui_.filter->text().toLower(); + const bool hide_with_covers = filter_without_covers_->isChecked(); + const bool hide_without_covers = filter_with_covers_->isChecked(); + + for (int i=0 ; icount() ; ++i) { + QListWidgetItem* item = ui_.albums->item(i); + QString text = item->text(); + bool has_cover = item->icon().cacheKey() != no_cover_icon_.cacheKey(); + + item->setHidden((!filter.isEmpty() && !text.toLower().contains(filter)) || + (has_cover && hide_with_covers) || + (!has_cover && hide_without_covers)); + } +} diff --git a/src/albumcovermanager.h b/src/albumcovermanager.h new file mode 100644 index 000000000..87fef13ec --- /dev/null +++ b/src/albumcovermanager.h @@ -0,0 +1,59 @@ +#ifndef ALBUMCOVERMANAGER_H +#define ALBUMCOVERMANAGER_H + +#include +#include + +#include + +#include "ui_covermanager.h" +#include "backgroundthread.h" +#include "albumcoverloader.h" + +class LibraryBackend; + +class AlbumCoverManager : public QDialog { + Q_OBJECT + public: + AlbumCoverManager(QWidget *parent = 0); + + static const char* kSettingsGroup; + + void Reset(); + + public slots: + void SetBackend(boost::shared_ptr backend); + + protected: + void showEvent(QShowEvent *); + void closeEvent(QCloseEvent *); + + private slots: + void ArtistChanged(QListWidgetItem* current); + void CoverLoaderInitialised(); + void CoverImageLoaded(quint64 id, const QImage& image); + void UpdateFilter(); + + private: + enum ArtistItemType { + All_Artists, + Specific_Artist, + }; + + private: + Ui::CoverManager ui_; + boost::shared_ptr backend_; + + QAction* filter_all_; + QAction* filter_with_covers_; + QAction* filter_without_covers_; + + BackgroundThread* cover_loader_; + QMap cover_loading_tasks_; + + QIcon artist_icon_; + QIcon all_artists_icon_; + QIcon no_cover_icon_; +}; + +#endif // ALBUMCOVERMANAGER_H diff --git a/src/albumcovermanager.ui b/src/albumcovermanager.ui new file mode 100644 index 000000000..cceee6b2d --- /dev/null +++ b/src/albumcovermanager.ui @@ -0,0 +1,175 @@ + + + CoverManager + + + + 0 + 0 + 827 + 662 + + + + Cover Manager + + + + 0 + + + + + Qt::Horizontal + + + + true + + + + + + + + 0 + + + + + + :/clear.png:/clear.png + + + true + + + + + + + Enter search terms here + + + + + + + View + + + + :/view-choose.png:/view-choose.png + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Fetch Missing Covers + + + + :/download.png:/download.png + + + + + + + + + false + + + + 120 + 120 + + + + QAbstractItemView::ScrollPerPixel + + + QListView::LeftToRight + + + true + + + QListView::Adjust + + + 2 + + + QListView::IconMode + + + true + + + true + + + + + + + + + + + + LineEdit + QLineEdit +
lineedit.h
+
+
+ + + + + + clear + clicked() + filter + clear() + + + 329 + 13 + + + 367 + 14 + + + + + clear + clicked() + filter + setFocus() + + + 334 + 20 + + + 401 + 13 + + + + +
diff --git a/src/clementine_el.ts b/src/clementine_el.ts index fb0ea8d1b..613f318e5 100644 --- a/src/clementine_el.ts +++ b/src/clementine_el.ts @@ -567,6 +567,10 @@ p, li { white-space: pre-wrap; } &Hide tray icon + + Configure &Global Shortcuts... + + MultiLoadingIndicator @@ -768,6 +772,109 @@ p, li { white-space: pre-wrap; } Show a notification when I change the volume + + Include album art in the notification + + + + + ShortcutsDialog + + Configure Shortcuts + + + + Play + Αναπαραγωγή + + + Pause + Παύση + + + Play/Pause + + + + Stop + Σταμάτημα + + + Stop Playing After Current Track + + + + Next Track + + + + Previous Track + + + + Increase Volume + + + + Decrease Volume + + + + Mute Volume + + + + Seek Forwards + + + + Seek Backwards + + + + Shortcut + + + + Alternate + + + + &Defaults + + + + &OK + + + + &Cancel + + + + Shortcut for Selected Action + + + + &None + + + + De&fault + + + + &Custom + + + + Non&e + + + + Default key: + + SomaFMService diff --git a/src/clementine_es.ts b/src/clementine_es.ts index 89601ddf9..acff5d253 100644 --- a/src/clementine_es.ts +++ b/src/clementine_es.ts @@ -550,6 +550,10 @@ p, li { white-space: pre-wrap; } &Hide tray icon + + Configure &Global Shortcuts... + + MultiLoadingIndicator @@ -751,6 +755,109 @@ p, li { white-space: pre-wrap; } Show a notification when I change the volume + + Include album art in the notification + + + + + ShortcutsDialog + + Configure Shortcuts + + + + Play + Reproducir + + + Pause + Pausa + + + Play/Pause + + + + Stop + Detener + + + Stop Playing After Current Track + + + + Next Track + + + + Previous Track + + + + Increase Volume + + + + Decrease Volume + + + + Mute Volume + + + + Seek Forwards + + + + Seek Backwards + + + + Shortcut + + + + Alternate + + + + &Defaults + + + + &OK + + + + &Cancel + + + + Shortcut for Selected Action + + + + &None + + + + De&fault + + + + &Custom + + + + Non&e + + + + Default key: + + SomaFMService diff --git a/src/clementine_ru.ts b/src/clementine_ru.ts index 3d4efd85a..80ddb8476 100644 --- a/src/clementine_ru.ts +++ b/src/clementine_ru.ts @@ -545,6 +545,10 @@ p, li { white-space: pre-wrap; } &Hide tray icon + + Configure &Global Shortcuts... + + MultiLoadingIndicator @@ -746,6 +750,109 @@ p, li { white-space: pre-wrap; } Show a notification when I change the volume + + Include album art in the notification + + + + + ShortcutsDialog + + Configure Shortcuts + + + + Play + Воспроизвести + + + Pause + Пауза + + + Play/Pause + + + + Stop + Стоп + + + Stop Playing After Current Track + + + + Next Track + + + + Previous Track + + + + Increase Volume + + + + Decrease Volume + + + + Mute Volume + + + + Seek Forwards + + + + Seek Backwards + + + + Shortcut + + + + Alternate + + + + &Defaults + + + + &OK + + + + &Cancel + + + + Shortcut for Selected Action + + + + &None + + + + De&fault + + + + &Custom + + + + Non&e + + + + Default key: + + SomaFMService diff --git a/src/library.cpp b/src/library.cpp index f614ec928..1ba820e9a 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -45,6 +45,8 @@ void Library::BackendInitialised() { dir_model_->SetBackend(backend_->Worker()); + emit BackendReady(backend_->Worker()); + if (--waiting_for_threads_ == 0) Initialise(); } @@ -339,17 +341,17 @@ void Library::LazyPopulate(LibraryItem* item) { break; case LibraryItem::Type_CompilationAlbum: - foreach (const Song& song, backend_->Worker()->GetCompilationSongs(query_options_, item->key)) + foreach (const Song& song, backend_->Worker()->GetCompilationSongs(item->key, query_options_)) CreateSongNode(false, song, item); break; case LibraryItem::Type_Artist: - foreach (const QString& album, backend_->Worker()->GetAlbumsByArtist(query_options_, item->key)) + foreach (const QString& album, backend_->Worker()->GetAlbumsByArtist(item->key, query_options_)) CreateAlbumNode(false, album, item, false); break; case LibraryItem::Type_Album: - foreach (const Song& song, backend_->Worker()->GetSongs(query_options_, item->parent->key, item->key)) + foreach (const Song& song, backend_->Worker()->GetSongs(item->parent->key, item->key, query_options_)) CreateSongNode(false, song, item); break; diff --git a/src/library.h b/src/library.h index 6d92751a6..3f27df8c6 100644 --- a/src/library.h +++ b/src/library.h @@ -50,6 +50,8 @@ class Library : public SimpleTreeModel { void ScanStarted(); void ScanFinished(); + void BackendReady(boost::shared_ptr backend); + public slots: void SetFilterAge(int age); void SetFilterText(const QString& text); diff --git a/src/librarybackend.cpp b/src/librarybackend.cpp index 0d88b3a2b..c15b48f95 100644 --- a/src/librarybackend.cpp +++ b/src/librarybackend.cpp @@ -321,11 +321,17 @@ QStringList LibraryBackend::GetAllArtists(const QueryOptions& opt) { return ret; } -QStringList LibraryBackend::GetAlbumsByArtist(const QueryOptions& opt, const QString& artist) { +QStringList LibraryBackend::GetAllAlbums(const QueryOptions &opt) { + return GetAlbumsByArtist(QString(), opt); +} + +QStringList LibraryBackend::GetAlbumsByArtist(const QString& artist, const QueryOptions& opt) { LibraryQuery query(opt); query.SetColumnSpec("DISTINCT album"); query.AddCompilationRequirement(false); - query.AddWhere("artist", artist); + + if (!artist.isNull()) + query.AddWhere("artist", artist); QSqlQuery q(query.Query(Connect())); q.exec(); @@ -338,7 +344,7 @@ QStringList LibraryBackend::GetAlbumsByArtist(const QueryOptions& opt, const QSt return ret; } -SongList LibraryBackend::GetSongs(const QueryOptions& opt, const QString& artist, const QString& album) { +SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, const QueryOptions& opt) { LibraryQuery query(opt); query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec)); query.AddCompilationRequirement(false); @@ -402,7 +408,7 @@ QStringList LibraryBackend::GetCompilationAlbums(const QueryOptions& opt) { return ret; } -SongList LibraryBackend::GetCompilationSongs(const QueryOptions& opt, const QString& album) { +SongList LibraryBackend::GetCompilationSongs(const QString& album, const QueryOptions& opt) { LibraryQuery query(opt); query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec)); query.AddCompilationRequirement(true); @@ -515,4 +521,33 @@ void LibraryBackend::UpdateCompilations(QSqlQuery& find_songs, QSqlQuery& update CheckErrors(update.lastError()); } +QList + LibraryBackend::GetAlbumArtInfo(const QString& artist, + const QueryOptions& opt) { + QList ret; + LibraryQuery query(opt); + query.SetColumnSpec("album, art_automatic, art_manual"); + query.SetOrderBy("album"); + if (!artist.isNull()) + query.AddWhere("artist", artist); + + QSqlQuery q(query.Query(Connect())); + q.exec(); + if (CheckErrors(q.lastError())) return ret; + + QString last_album; + while (q.next()) { + if (q.value(0).toString() == last_album) + continue; + + AlbumArtInfo info; + info.album_name = q.value(0).toString(); + info.art_automatic = q.value(1).toString(); + info.art_manual = q.value(2).toString(); + ret << info; + + last_album = info.album_name; + } + return ret; +} diff --git a/src/librarybackend.h b/src/librarybackend.h index 4b05866eb..919b7c5c0 100644 --- a/src/librarybackend.h +++ b/src/librarybackend.h @@ -9,8 +9,7 @@ #include "directory.h" #include "song.h" - -struct QueryOptions; +#include "libraryquery.h" class LibraryBackend : public QObject { Q_OBJECT @@ -18,6 +17,12 @@ class LibraryBackend : public QObject { public: LibraryBackend(QObject* parent = 0); + struct AlbumArtInfo { + QString album_name; + QString art_automatic; + QString art_manual; + }; + // This actually refers to the location of the sqlite database static QString DefaultDirectory(); @@ -29,13 +34,16 @@ class LibraryBackend : public QObject { SongList FindSongsInDirectory(int id); - QStringList GetAllArtists(const QueryOptions& opt); - QStringList GetAlbumsByArtist(const QueryOptions& opt, const QString& artist); - SongList GetSongs(const QueryOptions& opt, const QString& artist, const QString& album); + QStringList GetAllArtists(const QueryOptions& opt = QueryOptions()); + QStringList GetAllAlbums(const QueryOptions& opt = QueryOptions()); + QStringList GetAlbumsByArtist(const QString& artist, const QueryOptions& opt = QueryOptions()); + SongList GetSongs(const QString& artist, const QString& album, const QueryOptions& opt = QueryOptions()); - bool HasCompilations(const QueryOptions& opt); - QStringList GetCompilationAlbums(const QueryOptions& opt); - SongList GetCompilationSongs(const QueryOptions& opt, const QString& album); + bool HasCompilations(const QueryOptions& opt = QueryOptions()); + QStringList GetCompilationAlbums(const QueryOptions& opt = QueryOptions()); + SongList GetCompilationSongs(const QString& album, const QueryOptions& opt = QueryOptions()); + + QList GetAlbumArtInfo(const QString& artist = QString(), const QueryOptions& opt = QueryOptions()); Song GetSongById(int id); diff --git a/src/libraryquery.cpp b/src/libraryquery.cpp index 8d5652bdb..5f72a958c 100644 --- a/src/libraryquery.cpp +++ b/src/libraryquery.cpp @@ -53,6 +53,9 @@ QSqlQuery LibraryQuery::Query(QSqlDatabase db) const { if (!where_clauses_.isEmpty()) sql += " WHERE " + where_clauses_.join(" AND "); + if (!order_by_.isEmpty()) + sql += " ORDER BY " + order_by_; + QSqlQuery q(sql, db); // Bind values diff --git a/src/libraryquery.h b/src/libraryquery.h index e18337ddf..e6ec9ce7f 100644 --- a/src/libraryquery.h +++ b/src/libraryquery.h @@ -24,6 +24,7 @@ class LibraryQuery { LibraryQuery(const QueryOptions& options); void SetColumnSpec(const QString& spec) { column_spec_ = spec; } + void SetOrderBy(const QString& order_by) { order_by_ = order_by; } void AddWhere(const QString& column, const QVariant& value); void AddCompilationRequirement(bool compilation); @@ -31,6 +32,7 @@ class LibraryQuery { private: QString column_spec_; + QString order_by_; QStringList where_clauses_; QVariantList bound_values_; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c3cc5bec6..4766291ce 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -18,6 +18,7 @@ #include "about.h" #include "addstreamdialog.h" #include "stylesheetloader.h" +#include "albumcovermanager.h" #include "qxtglobalshortcut.h" @@ -54,6 +55,7 @@ MainWindow::MainWindow(QWidget *parent) settings_dialog_(new SettingsDialog(this)), add_stream_dialog_(new AddStreamDialog(this)), shortcuts_dialog_(new ShortcutsDialog(this)), + cover_manager_(new AlbumCoverManager(this)), playlist_menu_(new QMenu(this)), library_sort_model_(new QSortFilterProxyModel(this)), track_position_timer_(new QTimer(this)) @@ -114,6 +116,7 @@ MainWindow::MainWindow(QWidget *parent) connect(ui_.action_add_stream, SIGNAL(triggered()), SLOT(AddStream())); connect(ui_.action_hide_tray_icon, SIGNAL(triggered()), SLOT(HideShowTrayIcon())); connect(ui_.action_global_shortcuts, SIGNAL(triggered()), shortcuts_dialog_, SLOT(show())); + connect(ui_.action_cover_manager, SIGNAL(triggered()), cover_manager_, SLOT(show())); // Give actions to buttons ui_.forward_button->setDefaultAction(ui_.action_next_track); @@ -165,6 +168,8 @@ MainWindow::MainWindow(QWidget *parent) connect(library_, SIGNAL(TotalSongCountUpdated(int)), ui_.library_view, SLOT(TotalSongCountUpdated(int))); connect(library_, SIGNAL(ScanStarted()), SLOT(LibraryScanStarted())); connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished())); + connect(library_, SIGNAL(BackendReady(boost::shared_ptr)), + cover_manager_, SLOT(SetBackend(boost::shared_ptr))); // Age filters QActionGroup* filter_age_group = new QActionGroup(this); diff --git a/src/mainwindow.h b/src/mainwindow.h index 614965282..a2fa75092 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -22,6 +22,7 @@ class SettingsDialog; class About; class AddStreamDialog; class ShortcutsDialog; +class AlbumCoverManager; class QSortFilterProxyModel; class SystemTrayIcon; @@ -104,6 +105,7 @@ class MainWindow : public QMainWindow { SettingsDialog* settings_dialog_; AddStreamDialog* add_stream_dialog_; ShortcutsDialog* shortcuts_dialog_; + AlbumCoverManager* cover_manager_; QMenu* playlist_menu_; QAction* playlist_play_pause_; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6c6f702c1..8279190b8 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -309,7 +309,11 @@ - + + + Enter search terms here + + @@ -353,6 +357,9 @@ false + + false + @@ -434,7 +441,7 @@ 0 0 804 - 24 + 21 @@ -478,8 +485,15 @@ + + + Tools + + + + @@ -706,9 +720,23 @@ Configure &Global Shortcuts... + + + + :/download.png:/download.png + + + Cover Manager + + + + LineEdit + QLineEdit +
lineedit.h
+
BlockAnalyzer QWidget diff --git a/src/src.pro b/src/src.pro index 25411cc4b..bee47a560 100644 --- a/src/src.pro +++ b/src/src.pro @@ -56,7 +56,9 @@ SOURCES += main.cpp \ addstreamdialog.cpp \ savedradio.cpp \ stylesheetloader.cpp \ - shortcutsdialog.cpp + shortcutsdialog.cpp \ + covermanager.cpp \ + coverloader.cpp HEADERS += mainwindow.h \ player.h \ library.h \ @@ -113,7 +115,9 @@ HEADERS += mainwindow.h \ addstreamdialog.h \ savedradio.h \ stylesheetloader.h \ - shortcutsdialog.h + shortcutsdialog.h \ + covermanager.h \ + coverloader.h FORMS += mainwindow.ui \ libraryconfig.ui \ fileview.ui \ @@ -127,7 +131,8 @@ FORMS += mainwindow.ui \ lastfmconfigdialog.ui \ about.ui \ addstreamdialog.ui \ - shortcutsdialog.ui + shortcutsdialog.ui \ + covermanager.ui RESOURCES += ../data/data.qrc \ translations.qrc OTHER_FILES += ../data/schema.sql \