Add a spotify global search provider
This commit is contained in:
parent
dbe8ffd5de
commit
8dea8a2664
@ -671,6 +671,7 @@ endif(HAVE_LIBLASTFM)
|
|||||||
|
|
||||||
if(HAVE_SPOTIFY)
|
if(HAVE_SPOTIFY)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
|
globalsearch/spotifysearchprovider.cpp
|
||||||
internet/spotifyblobdownloader.cpp
|
internet/spotifyblobdownloader.cpp
|
||||||
internet/spotifysearchplaylisttype.cpp
|
internet/spotifysearchplaylisttype.cpp
|
||||||
internet/spotifyserver.cpp
|
internet/spotifyserver.cpp
|
||||||
@ -680,6 +681,7 @@ if(HAVE_SPOTIFY)
|
|||||||
resolvers/spotifyresolver.cpp
|
resolvers/spotifyresolver.cpp
|
||||||
)
|
)
|
||||||
list(APPEND HEADERS
|
list(APPEND HEADERS
|
||||||
|
globalsearch/spotifysearchprovider.h
|
||||||
internet/spotifyblobdownloader.h
|
internet/spotifyblobdownloader.h
|
||||||
internet/spotifyserver.h
|
internet/spotifyserver.h
|
||||||
internet/spotifyservice.h
|
internet/spotifyservice.h
|
||||||
|
@ -31,31 +31,7 @@ GlobalSearchItemDelegate::GlobalSearchItemDelegate(GlobalSearchWidget* widget)
|
|||||||
: QStyledItemDelegate(widget),
|
: QStyledItemDelegate(widget),
|
||||||
widget_(widget)
|
widget_(widget)
|
||||||
{
|
{
|
||||||
no_cover_ = ScaleAndPad(QImage(":nocover.png"));
|
no_cover_ = QPixmap::fromImage(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize GlobalSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option,
|
QSize GlobalSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option,
|
||||||
|
@ -32,8 +32,6 @@ public:
|
|||||||
static const int kArtMargin;
|
static const int kArtMargin;
|
||||||
static const int kWordPadding;
|
static const int kWordPadding;
|
||||||
|
|
||||||
static QPixmap ScaleAndPad(const QImage& image);
|
|
||||||
|
|
||||||
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||||
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||||
const QModelIndex& index) const;
|
const QModelIndex& index) const;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "globalsearch.h"
|
#include "globalsearch.h"
|
||||||
#include "globalsearchitemdelegate.h"
|
#include "globalsearchitemdelegate.h"
|
||||||
#include "globalsearchsortmodel.h"
|
#include "globalsearchsortmodel.h"
|
||||||
@ -25,6 +26,10 @@
|
|||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
#include "widgets/stylehelper.h"
|
#include "widgets/stylehelper.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
# include "spotifysearchprovider.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
@ -75,9 +80,14 @@ GlobalSearchWidget::~GlobalSearchWidget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GlobalSearchWidget::Init(LibraryBackendInterface* library) {
|
void GlobalSearchWidget::Init(LibraryBackendInterface* library) {
|
||||||
|
// Add providers
|
||||||
engine_->AddProvider(new LibrarySearchProvider(
|
engine_->AddProvider(new LibrarySearchProvider(
|
||||||
library, tr("Library"), IconLoader::Load("folder-sound"), engine_));
|
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
|
// The style helper's base color doesn't get initialised until after the
|
||||||
// constructor.
|
// constructor.
|
||||||
QPalette view_palette = view_->palette();
|
QPalette view_palette = view_->palette();
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "searchprovider.h"
|
#include "searchprovider.h"
|
||||||
#include "core/boundfuturewatcher.h"
|
#include "core/boundfuturewatcher.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
|
||||||
const int SearchProvider::kArtHeight = 32;
|
const int SearchProvider::kArtHeight = 32;
|
||||||
@ -87,3 +88,31 @@ void BlockingSearchProvider::BlockingSearchFinished() {
|
|||||||
emit ResultsAvailable(id, watcher->result());
|
emit ResultsAvailable(id, watcher->result());
|
||||||
emit SearchFinished(id);
|
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;
|
||||||
|
}
|
||||||
|
@ -83,6 +83,8 @@ public:
|
|||||||
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
||||||
virtual void LoadTracksAsync(int id, const Result& result) = 0;
|
virtual void LoadTracksAsync(int id, const Result& result) = 0;
|
||||||
|
|
||||||
|
static QImage ScaleAndPad(const QImage& image);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ResultsAvailable(int id, const SearchProvider::ResultList& results);
|
void ResultsAvailable(int id, const SearchProvider::ResultList& results);
|
||||||
void SearchFinished(int id);
|
void SearchFinished(int id);
|
||||||
|
125
src/globalsearch/spotifysearchprovider.cpp
Normal file
125
src/globalsearch/spotifysearchprovider.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/* This file is part of Clementine.
|
||||||
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<SpotifyService>();
|
||||||
|
|
||||||
|
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<QString, PendingState>::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<QString, int>::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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
59
src/globalsearch/spotifysearchprovider.h
Normal file
59
src/globalsearch/spotifysearchprovider.h
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/* This file is part of Clementine.
|
||||||
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
|
||||||
|
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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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<QString, PendingState> queries_;
|
||||||
|
QMap<QString, int> pending_art_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SPOTIFYSEARCHPROVIDER_H
|
Loading…
x
Reference in New Issue
Block a user