diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c01a10559..33aa39a2c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -671,6 +671,7 @@ endif(HAVE_LIBLASTFM) if(HAVE_SPOTIFY) list(APPEND SOURCES + globalsearch/spotifysearchprovider.cpp internet/spotifyblobdownloader.cpp internet/spotifysearchplaylisttype.cpp internet/spotifyserver.cpp @@ -680,6 +681,7 @@ if(HAVE_SPOTIFY) resolvers/spotifyresolver.cpp ) list(APPEND HEADERS + globalsearch/spotifysearchprovider.h internet/spotifyblobdownloader.h internet/spotifyserver.h internet/spotifyservice.h diff --git a/src/globalsearch/globalsearchitemdelegate.cpp b/src/globalsearch/globalsearchitemdelegate.cpp index 467d8093b..d4b03d84a 100644 --- a/src/globalsearch/globalsearchitemdelegate.cpp +++ b/src/globalsearch/globalsearchitemdelegate.cpp @@ -31,31 +31,7 @@ GlobalSearchItemDelegate::GlobalSearchItemDelegate(GlobalSearchWidget* widget) : QStyledItemDelegate(widget), widget_(widget) { - no_cover_ = ScaleAndPad(QImage(":nocover.png")); -} - -QPixmap GlobalSearchItemDelegate::ScaleAndPad(const QImage& image) { - if (image.isNull()) - return QPixmap(); - - if (image.size() == QSize(kHeight, kHeight)) - return QPixmap::fromImage(image); - - // Scale the image down - QImage copy; - copy = image.scaled(QSize(kHeight, kHeight), - Qt::KeepAspectRatio, Qt::SmoothTransformation); - - // Pad the image to kHeight x kHeight - QImage padded_image(kHeight, kHeight, QImage::Format_ARGB32); - padded_image.fill(0); - - QPainter p(&padded_image); - p.drawImage((kHeight - copy.width()) / 2, (kHeight - copy.height()) / 2, - copy); - p.end(); - - return QPixmap::fromImage(padded_image); + no_cover_ = QPixmap::fromImage(QImage(":nocover.png")); } QSize GlobalSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option, diff --git a/src/globalsearch/globalsearchitemdelegate.h b/src/globalsearch/globalsearchitemdelegate.h index a07f5704f..49300d862 100644 --- a/src/globalsearch/globalsearchitemdelegate.h +++ b/src/globalsearch/globalsearchitemdelegate.h @@ -32,8 +32,6 @@ public: static const int kArtMargin; static const int kWordPadding; - static QPixmap ScaleAndPad(const QImage& image); - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; diff --git a/src/globalsearch/globalsearchwidget.cpp b/src/globalsearch/globalsearchwidget.cpp index b438a2ff2..a9b38c5c4 100644 --- a/src/globalsearch/globalsearchwidget.cpp +++ b/src/globalsearch/globalsearchwidget.cpp @@ -15,6 +15,7 @@ along with Clementine. If not, see . */ +#include "config.h" #include "globalsearch.h" #include "globalsearchitemdelegate.h" #include "globalsearchsortmodel.h" @@ -25,6 +26,10 @@ #include "core/utilities.h" #include "widgets/stylehelper.h" +#ifdef HAVE_SPOTIFY +# include "spotifysearchprovider.h" +#endif + #include #include #include @@ -75,9 +80,14 @@ GlobalSearchWidget::~GlobalSearchWidget() { } void GlobalSearchWidget::Init(LibraryBackendInterface* library) { + // Add providers engine_->AddProvider(new LibrarySearchProvider( library, tr("Library"), IconLoader::Load("folder-sound"), engine_)); +#ifdef HAVE_SPOTIFY + engine_->AddProvider(new SpotifySearchProvider(engine_)); +#endif + // The style helper's base color doesn't get initialised until after the // constructor. QPalette view_palette = view_->palette(); diff --git a/src/globalsearch/searchprovider.cpp b/src/globalsearch/searchprovider.cpp index 4f9776b9e..fd8e8e3a7 100644 --- a/src/globalsearch/searchprovider.cpp +++ b/src/globalsearch/searchprovider.cpp @@ -18,6 +18,7 @@ #include "searchprovider.h" #include "core/boundfuturewatcher.h" +#include #include const int SearchProvider::kArtHeight = 32; @@ -87,3 +88,31 @@ void BlockingSearchProvider::BlockingSearchFinished() { emit ResultsAvailable(id, watcher->result()); emit SearchFinished(id); } + +QImage SearchProvider::ScaleAndPad(const QImage& image) { + if (image.isNull()) + return QImage(); + + const QSize target_size = QSize(kArtHeight, kArtHeight); + + if (image.size() == target_size) + return image; + + // Scale the image down + QImage copy; + copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + // Pad the image to kHeight x kHeight + if (copy.size() == target_size) + return copy; + + QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32); + padded_image.fill(0); + + QPainter p(&padded_image); + p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, + copy); + p.end(); + + return padded_image; +} diff --git a/src/globalsearch/searchprovider.h b/src/globalsearch/searchprovider.h index 48fa39603..2d38cd63d 100644 --- a/src/globalsearch/searchprovider.h +++ b/src/globalsearch/searchprovider.h @@ -83,6 +83,8 @@ public: // ResultsAvailable. Must emit TracksLoaded exactly once with this ID. virtual void LoadTracksAsync(int id, const Result& result) = 0; + static QImage ScaleAndPad(const QImage& image); + signals: void ResultsAvailable(int id, const SearchProvider::ResultList& results); void SearchFinished(int id); diff --git a/src/globalsearch/spotifysearchprovider.cpp b/src/globalsearch/spotifysearchprovider.cpp new file mode 100644 index 000000000..a33733e12 --- /dev/null +++ b/src/globalsearch/spotifysearchprovider.cpp @@ -0,0 +1,125 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include "spotifysearchprovider.h" +#include "internet/internetmodel.h" +#include "internet/spotifyserver.h" +#include "internet/spotifyservice.h" + +SpotifySearchProvider::SpotifySearchProvider(QObject* parent) + : SearchProvider("Spotify", QIcon(":icons/svg/spotify.svg"), parent), + server_(NULL), + service_(NULL) +{ +} + +SpotifyServer* SpotifySearchProvider::server() { + if (server_) + return server_; + + if (!service_) + service_ = InternetModel::Service(); + + if (service_->login_state() != SpotifyService::LoginState_LoggedIn) + return NULL; + + server_ = service_->server(); + connect(server_, SIGNAL(SearchResults(protobuf::SearchResponse)), + SLOT(SearchFinishedSlot(protobuf::SearchResponse))); + connect(server_, SIGNAL(ImageLoaded(QString,QImage)), + SLOT(ArtLoadedSlot(QString,QImage))); + connect(server_, SIGNAL(destroyed()), SLOT(ServerDestroyed())); + + return service_->server(); +} + +void SpotifySearchProvider::ServerDestroyed() { + server_ = NULL; +} + +void SpotifySearchProvider::SearchAsync(int id, const QString& query) { + SpotifyServer* s = server(); + if (!s) { + emit SearchFinished(id); + return; + } + + PendingState state; + state.orig_id_ = id; + state.tokens_ = TokenizeQuery(query); + + const QString query_string = state.tokens_.join(" "); + s->Search(query_string, 25); + queries_[query_string] = state; +} + +void SpotifySearchProvider::SearchFinishedSlot(const protobuf::SearchResponse& response) { + QString query_string = QString::fromUtf8(response.request().query().c_str()); + QMap::iterator it = queries_.find(query_string); + if (it == queries_.end()) + return; + + PendingState state = it.value(); + queries_.erase(it); + + ResultList ret; + for (int i = 0; i < response.result_size(); ++i) { + const protobuf::Track& track = response.result(i); + + Result result(this); + result.type_ = Result::Type_Track; + SpotifyService::SongFromProtobuf(track, &result.metadata_); + result.match_quality_ = MatchQuality(state.tokens_, result.metadata_.title()); + + ret << result; + } + + emit ResultsAvailable(state.orig_id_, ret); + emit SearchFinished(state.orig_id_); +} + +void SpotifySearchProvider::LoadArtAsync(int id, const Result& result) { + SpotifyServer* s = server(); + if (!s) { + emit ArtLoaded(id, QImage()); + return; + } + + QString image_id = result.metadata_.url().path(); + if (image_id.startsWith('/')) + image_id.remove(0, 1); + + pending_art_[image_id] = id; + s->LoadImage(image_id); +} + +void SpotifySearchProvider::ArtLoadedSlot(const QString& id, const QImage& image) { + QMap::iterator it = pending_art_.find(id); + if (it == pending_art_.end()) + return; + + const int orig_id = it.value(); + pending_art_.erase(it); + + emit ArtLoaded(orig_id, ScaleAndPad(image)); +} + +void SpotifySearchProvider::LoadTracksAsync(int id, const Result& result) { + emit TracksLoaded(id, SongList()); +} + + diff --git a/src/globalsearch/spotifysearchprovider.h b/src/globalsearch/spotifysearchprovider.h new file mode 100644 index 000000000..676a7a646 --- /dev/null +++ b/src/globalsearch/spotifysearchprovider.h @@ -0,0 +1,59 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#ifndef SPOTIFYSEARCHPROVIDER_H +#define SPOTIFYSEARCHPROVIDER_H + +#include "searchprovider.h" +#include "spotifyblob/common/spotifymessages.pb.h" + +class SpotifyServer; +class SpotifyService; + + +class SpotifySearchProvider : public SearchProvider { + Q_OBJECT + +public: + SpotifySearchProvider(QObject* parent = 0); + + void SearchAsync(int id, const QString& query); + void LoadArtAsync(int id, const Result& result); + void LoadTracksAsync(int id, const Result& result); + +private slots: + void ServerDestroyed(); + void SearchFinishedSlot(const protobuf::SearchResponse& response); + void ArtLoadedSlot(const QString& id, const QImage& image); + +private: + struct PendingState { + int orig_id_; + QStringList tokens_; + }; + + SpotifyServer* server(); + +private: + SpotifyServer* server_; + SpotifyService* service_; + + QMap queries_; + QMap pending_art_; +}; + +#endif // SPOTIFYSEARCHPROVIDER_H