Show spotify search results in a separate playlist tab rather than in a tree in the sidebar.

This commit is contained in:
David Sansome 2011-04-28 17:50:45 +00:00
parent 02e2a84e70
commit 06852aaeb7
32 changed files with 383 additions and 113 deletions

View File

@ -314,5 +314,9 @@
<file>schema/schema-30.sql</file>
<file>schema/schema-31.sql</file>
<file>schema/schema-32.sql</file>
<file>icons/22x22/edit-find.png</file>
<file>icons/32x32/edit-find.png</file>
<file>icons/48x48/edit-find.png</file>
<file>schema/schema-33.sql</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,3 @@
ALTER TABLE playlists ADD COLUMN special_type TEXT;
UPDATE schema_version SET version=33;

View File

@ -91,7 +91,7 @@ class DigitallyImportedServiceBase(clementine.RadioService):
clementine.RadioService.__init__(self, self.SERVICE_NAME, model)
self.url_handler = DigitallyImportedUrlHandler(self)
clementine.player.AddUrlHandler(self.url_handler)
clementine.player.RegisterUrlHandler(self.url_handler)
self.network = clementine.NetworkAccessManager(self)
self.path = os.path.dirname(__file__)

View File

@ -145,6 +145,7 @@ set(SOURCES
playlist/playlisttabbar.cpp
playlist/playlistundocommands.cpp
playlist/playlistview.cpp
playlist/specialplaylisttype.cpp
playlist/queue.cpp
playlist/queuemanager.cpp
playlist/songloaderinserter.cpp
@ -624,6 +625,7 @@ if(HAVE_SPOTIFY)
radio/spotifyconfig.cpp
radio/spotifyserver.cpp
radio/spotifyservice.cpp
radio/spotifysearchplaylisttype.cpp
radio/spotifyurlhandler.cpp
)
list(APPEND HEADERS

View File

@ -32,7 +32,7 @@
#include <QVariant>
const char* Database::kDatabaseFilename = "clementine.db";
const int Database::kSchemaVersion = 32;
const int Database::kSchemaVersion = 33;
const char* Database::kMagicAllSongsTables = "%allsongstables";
int Database::sNextConnectionId = 1;

View File

@ -481,7 +481,7 @@ void Player::InvalidSongRequested(const QUrl& url) {
NextItem(Engine::Auto);
}
void Player::AddUrlHandler(UrlHandler* handler) {
void Player::RegisterUrlHandler(UrlHandler* handler) {
const QString scheme = handler->scheme();
if (url_handlers_.contains(scheme)) {
@ -497,15 +497,15 @@ void Player::AddUrlHandler(UrlHandler* handler) {
SLOT(HandleLoadResult(UrlHandler::LoadResult)));
}
void Player::RemoveUrlHandler(UrlHandler* handler) {
void Player::UnregisterUrlHandler(UrlHandler* handler) {
const QString scheme = url_handlers_.key(handler);
if (scheme.isEmpty()) {
qLog(Warning) << "Tried to remove a URL handler for" << handler->scheme()
qLog(Warning) << "Tried to unregister a URL handler for" << handler->scheme()
<< "that wasn't registered";
return;
}
qLog(Info) << "Removed URL handler for" << scheme;
qLog(Info) << "Unregistered URL handler for" << scheme;
url_handlers_.remove(scheme);
disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(UrlHandlerDestroyed(QObject*)));
disconnect(handler, SIGNAL(AsyncLoadComplete(UrlHandler::LoadResult)),

View File

@ -50,8 +50,8 @@ public:
virtual PlaylistItemPtr GetItemAt(int pos) const = 0;
virtual PlaylistManagerInterface* playlists() const = 0;
virtual void AddUrlHandler(UrlHandler* handler) = 0;
virtual void RemoveUrlHandler(UrlHandler* handler) = 0;
virtual void RegisterUrlHandler(UrlHandler* handler) = 0;
virtual void UnregisterUrlHandler(UrlHandler* handler) = 0;
public slots:
virtual void ReloadSettings() = 0;
@ -119,8 +119,8 @@ public:
PlaylistItemPtr GetItemAt(int pos) const;
PlaylistManagerInterface* playlists() const { return playlists_; }
void AddUrlHandler(UrlHandler* handler);
void RemoveUrlHandler(UrlHandler* handler);
void RegisterUrlHandler(UrlHandler* handler);
void UnregisterUrlHandler(UrlHandler* handler);
public slots:
void ReloadSettings();

View File

@ -80,6 +80,7 @@ Playlist::Playlist(PlaylistBackend* backend,
TaskManager* task_manager,
LibraryBackend* library,
int id,
const QString& special_type,
QObject *parent)
: QAbstractListModel(parent),
is_loading_(false),
@ -97,7 +98,8 @@ Playlist::Playlist(PlaylistBackend* backend,
have_incremented_playcount_(false),
playlist_sequence_(NULL),
ignore_sorting_(false),
undo_stack_(new QUndoStack(this))
undo_stack_(new QUndoStack(this)),
special_type_(special_type)
{
undo_stack_->setUndoLimit(kUndoStackSize);

View File

@ -73,6 +73,7 @@ class Playlist : public QAbstractListModel {
TaskManager* task_manager,
LibraryBackend* library,
int id,
const QString& special_type = QString(),
QObject* parent = 0);
~Playlist();
@ -168,6 +169,9 @@ class Playlist : public QAbstractListModel {
bool stop_after_current() const;
bool is_dynamic() const { return dynamic_playlist_; }
QString special_type() const { return special_type_; }
void set_special_type(const QString& v) { special_type_ = v; }
const PlaylistItemPtr& item_at(int index) const { return items_[index]; }
const bool has_item_at(int index) const { return index >= 0 && index < rowCount(); }
@ -368,6 +372,8 @@ class Playlist : public QAbstractListModel {
ColumnAlignmentMap column_alignments_;
QList<SongInsertVetoListener*> veto_listeners_;
QString special_type_;
};
QDataStream& operator <<(QDataStream&, const Playlist*);

View File

@ -57,7 +57,8 @@ PlaylistBackend::PlaylistList PlaylistBackend::GetAllPlaylists() {
PlaylistList ret;
QSqlQuery q("SELECT ROWID, name, last_played, dynamic_playlist_type,"
" dynamic_playlist_data, dynamic_playlist_backend"
" dynamic_playlist_data, dynamic_playlist_backend,"
" special_type"
" FROM playlists"
" ORDER BY ui_order", db);
q.exec();
@ -72,6 +73,7 @@ PlaylistBackend::PlaylistList PlaylistBackend::GetAllPlaylists() {
p.dynamic_type = q.value(3).toString();
p.dynamic_data = q.value(4).toByteArray();
p.dynamic_backend = q.value(5).toString();
p.special_type = q.value(6).toString();
ret << p;
}
@ -83,7 +85,8 @@ PlaylistBackend::Playlist PlaylistBackend::GetPlaylist(int id) {
QSqlDatabase db(db_->Connect());
QSqlQuery q("SELECT ROWID, name, last_played, dynamic_playlist_type,"
" dynamic_playlist_data, dynamic_playlist_backend"
" dynamic_playlist_data, dynamic_playlist_backend,"
" special_type"
" FROM playlists"
" WHERE ROWID=:id", db);
q.bindValue(":id", id);
@ -100,6 +103,7 @@ PlaylistBackend::Playlist PlaylistBackend::GetPlaylist(int id) {
p.dynamic_type = q.value(3).toString();
p.dynamic_data = q.value(4).toByteArray();
p.dynamic_backend = q.value(5).toString();
p.special_type = q.value(6).toString();
return p;
}
@ -268,12 +272,15 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList& items,
transaction.Commit();
}
int PlaylistBackend::CreatePlaylist(const QString &name) {
int PlaylistBackend::CreatePlaylist(const QString &name,
const QString& special_type) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
QSqlQuery q("INSERT INTO playlists (name) VALUES (:name)", db);
QSqlQuery q("INSERT INTO playlists (name, special_type)"
" VALUES (:name, :special_type)", db);
q.bindValue(":name", name);
q.bindValue(":special_type", special_type);
q.exec();
if (db_->CheckErrors(q))
return -1;

View File

@ -46,6 +46,10 @@ class PlaylistBackend : public QObject {
QString dynamic_type;
QString dynamic_backend;
QByteArray dynamic_data;
// Special playlists have different behaviour, eg. the "spotify-search"
// type has a spotify search box at the top, replacing the ordinary filter.
QString special_type;
};
typedef QList<Playlist> PlaylistList;
typedef QFuture<PlaylistItemPtr> PlaylistItemFuture;
@ -58,7 +62,7 @@ class PlaylistBackend : public QObject {
void SetPlaylistOrder(const QList<int>& ids);
int CreatePlaylist(const QString& name);
int CreatePlaylist(const QString& name, const QString& special_type);
void SavePlaylistAsync(int playlist, const PlaylistItemList& items,
int last_played, smart_playlists::GeneratorPtr dynamic);
void RenamePlaylist(int id, const QString& new_name);

View File

@ -17,6 +17,7 @@
#include "playlistcontainer.h"
#include "playlistmanager.h"
#include "specialplaylisttype.h"
#include "ui_playlistcontainer.h"
#include "playlistparsers/playlistparser.h"
#include "ui/iconloader.h"
@ -181,6 +182,12 @@ void PlaylistContainer::SetViewModel(Playlist* playlist) {
ui_->redo->setDefaultAction(redo_);
emit UndoRedoActionsChanged(undo_, redo_);
// Implement special playlist behaviour
const SpecialPlaylistType* type = manager_->GetPlaylistType(playlist->special_type());
ui_->filter->set_hint(type->search_hint_text(playlist));
}
void PlaylistContainer::ActivePlaying() {
@ -207,8 +214,12 @@ void PlaylistContainer::UpdateActiveIcon(const QIcon& icon) {
}
void PlaylistContainer::PlaylistAdded(int id, const QString &name) {
int index = ui_->tab_bar->count();
ui_->tab_bar->InsertTab(id, index, name);
Playlist* playlist = manager_->playlist(id);
const SpecialPlaylistType* type = manager_->GetPlaylistType(playlist->special_type());
const int index = ui_->tab_bar->count();
const QIcon icon = type->icon(playlist);
ui_->tab_bar->InsertTab(id, index, name, icon);
// Are we startup up, should we select this tab?
if (starting_up_ && settings_.value("current_playlist", 1).toInt() == id) {
@ -336,15 +347,22 @@ void PlaylistContainer::SetTabBarHeight(int height) {
}
void PlaylistContainer::UpdateFilter() {
manager_->current()->proxy()->setFilterFixedString(filter_->text());
ui_->playlist->JumpToCurrentlyPlayingTrack();
Playlist* playlist = manager_->current();
SpecialPlaylistType* type = manager_->GetPlaylistType(playlist->special_type());
bool no_matches = manager_->current()->proxy()->rowCount() == 0 &&
manager_->current()->rowCount() > 0;
if (type->has_special_search_behaviour(playlist)) {
type->Search(filter_->text(), playlist);
} else {
manager_->current()->proxy()->setFilterFixedString(filter_->text());
ui_->playlist->JumpToCurrentlyPlayingTrack();
if (no_matches)
RepositionNoMatchesLabel(true);
no_matches_label_->setVisible(no_matches);
const bool no_matches = manager_->current()->proxy()->rowCount() == 0 &&
manager_->current()->rowCount() > 0;
if (no_matches)
RepositionNoMatchesLabel(true);
no_matches_label_->setVisible(no_matches);
}
}
void PlaylistContainer::resizeEvent(QResizeEvent* e) {

View File

@ -114,7 +114,7 @@
<item>
<widget class="LineEdit" name="filter">
<property name="hint" stdset="0">
<string>Playlist search</string>
<string/>
</property>
</widget>
</item>

View File

@ -18,6 +18,8 @@
#include "playlist.h"
#include "playlistbackend.h"
#include "playlistmanager.h"
#include "specialplaylisttype.h"
#include "core/logging.h"
#include "core/songloader.h"
#include "core/utilities.h"
#include "library/librarybackend.h"
@ -37,6 +39,7 @@ PlaylistManager::PlaylistManager(TaskManager* task_manager, QObject *parent)
library_backend_(NULL),
sequence_(NULL),
parser_(NULL),
default_playlist_type_(new DefaultPlaylistType),
current_(-1),
active_(-1)
{
@ -46,6 +49,9 @@ PlaylistManager::~PlaylistManager() {
foreach (const Data& data, playlists_.values()) {
delete data.p;
}
qDeleteAll(special_playlist_types_.values());
delete default_playlist_type_;
}
void PlaylistManager::Init(LibraryBackend* library_backend,
@ -60,7 +66,7 @@ void PlaylistManager::Init(LibraryBackend* library_backend,
connect(library_backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsDiscovered(SongList)));
foreach (const PlaylistBackend::Playlist& p, playlist_backend->GetAllPlaylists()) {
AddPlaylist(p.id, p.name);
AddPlaylist(p.id, p.name, p.special_type);
}
// If no playlist exists then make a new one
@ -85,8 +91,9 @@ const QItemSelection& PlaylistManager::selection(int id) const {
return it->selection;
}
Playlist* PlaylistManager::AddPlaylist(int id, const QString& name) {
Playlist* ret = new Playlist(playlist_backend_, task_manager_, library_backend_, id);
Playlist* PlaylistManager::AddPlaylist(int id, const QString& name,
const QString& special_type) {
Playlist* ret = new Playlist(playlist_backend_, task_manager_, library_backend_, id, special_type);
ret->set_sequence(sequence_);
connect(ret, SIGNAL(CurrentSongChanged(Song)), SIGNAL(CurrentSongChanged(Song)));
@ -110,16 +117,17 @@ Playlist* PlaylistManager::AddPlaylist(int id, const QString& name) {
return ret;
}
void PlaylistManager::New(const QString& name, const SongList& songs) {
void PlaylistManager::New(const QString& name, const SongList& songs,
const QString& special_type) {
if (name.isNull())
return;
int id = playlist_backend_->CreatePlaylist(name);
int id = playlist_backend_->CreatePlaylist(name, special_type);
if (id == -1)
qFatal("Couldn't create playlist");
Playlist* playlist = AddPlaylist(id, name);
Playlist* playlist = AddPlaylist(id, name, special_type);
playlist->InsertSongsOrLibraryItems(songs);
SetCurrentPlaylist(id);
@ -390,3 +398,35 @@ QString PlaylistManager::GetNameForNewPlaylist(const SongList& songs) {
return result;
}
void PlaylistManager::RegisterSpecialPlaylistType(SpecialPlaylistType* type) {
const QString name = type->name();
if (special_playlist_types_.contains(name)) {
qLog(Warning) << "Tried to register a special playlist type" << name
<< "but one was already registered";
return;
}
qLog(Info) << "Registered special playlist type" << name;
special_playlist_types_.insert(name, type);
}
void PlaylistManager::UnregisterSpecialPlaylistType(SpecialPlaylistType* type) {
const QString name = special_playlist_types_.key(type);
if (name.isEmpty()) {
qLog(Warning) << "Tried to unregister a special playlist type" << type->name()
<< "that wasn't registered";
return;
}
qLog(Info) << "Unregistered special playlist type" << name;
special_playlist_types_.remove(name);
}
SpecialPlaylistType* PlaylistManager::GetPlaylistType(const QString& type) const {
if (special_playlist_types_.contains(type)) {
return special_playlist_types_[type];
}
return default_playlist_type_;
}

View File

@ -31,6 +31,7 @@ class Playlist;
class PlaylistBackend;
class PlaylistParser;
class PlaylistSequence;
class SpecialPlaylistType;
class TaskManager;
class QModelIndex;
@ -69,8 +70,13 @@ public:
virtual PlaylistSequence* sequence() const = 0;
virtual PlaylistParser* parser() const = 0;
virtual void RegisterSpecialPlaylistType(SpecialPlaylistType* type) = 0;
virtual void UnregisterSpecialPlaylistType(SpecialPlaylistType* type) = 0;
virtual SpecialPlaylistType* GetPlaylistType(const QString& type) const = 0;
public slots:
virtual void New(const QString& name, const SongList& songs = SongList()) = 0;
virtual void New(const QString& name, const SongList& songs = SongList(),
const QString& special_type = QString()) = 0;
virtual void Load(const QString& filename) = 0;
virtual void Save(int id, const QString& filename) = 0;
virtual void Rename(int id, const QString& new_name) = 0;
@ -161,8 +167,13 @@ public:
PlaylistSequence* sequence() const { return sequence_; }
PlaylistParser* parser() const { return parser_; }
void RegisterSpecialPlaylistType(SpecialPlaylistType* type);
void UnregisterSpecialPlaylistType(SpecialPlaylistType* type);
SpecialPlaylistType* GetPlaylistType(const QString& type) const;
public slots:
void New(const QString& name, const SongList& songs = SongList());
void New(const QString& name, const SongList& songs = SongList(),
const QString& special_type = QString());
void Load(const QString& filename);
void Save(int id, const QString& filename);
void Rename(int id, const QString& new_name);
@ -198,7 +209,7 @@ private slots:
void LoadFinished(bool success);
private:
Playlist* AddPlaylist(int id, const QString& name);
Playlist* AddPlaylist(int id, const QString& name, const QString& special_type);
private:
struct Data {
@ -217,6 +228,9 @@ private:
// key = id
QMap<int, Data> playlists_;
QMap<QString, SpecialPlaylistType*> special_playlist_types_;
SpecialPlaylistType* default_playlist_type_;
int current_;
int active_;
};

View File

@ -204,11 +204,13 @@ void PlaylistTabBar::CurrentIndexChanged(int index) {
emit CurrentIdChanged(tabData(index).toInt());
}
void PlaylistTabBar::InsertTab(int id, int index, const QString& text) {
void PlaylistTabBar::InsertTab(int id, int index, const QString& text,
const QIcon& icon) {
suppress_current_changed_ = true;
insertTab(index, text);
setTabData(index, id);
setTabToolTip(index, text);
setTabIcon(index, icon);
suppress_current_changed_ = false;
if (currentIndex() == index)

View File

@ -19,6 +19,7 @@
#define PLAYLISTTABBAR_H
#include <QBasicTimer>
#include <QIcon>
#include <QTabBar>
class PlaylistManager;
@ -49,7 +50,8 @@ public:
void set_text_by_id(int id, const QString& text);
void RemoveTab(int id);
void InsertTab(int id, int index, const QString& text);
void InsertTab(int id, int index, const QString& text,
const QIcon& icon = QIcon());
signals:
void CurrentIdChanged(int id);

View File

@ -16,11 +16,10 @@
*/
#include "dynamicplaylistcontrols.h"
#include "playlistview.h"
#include "playlist.h"
#include "playlistheader.h"
#include "playlistdelegates.h"
#include "playlist.h"
#include "playlistheader.h"
#include "playlistview.h"
#include <QCleanlooksStyle>
#include <QPainter>

View File

@ -0,0 +1,43 @@
/* 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 "specialplaylisttype.h"
#include <QObject>
QIcon SpecialPlaylistType::icon(Playlist* playlist) const {
return QIcon();
}
QString SpecialPlaylistType::search_hint_text(Playlist* playlist) const {
return QObject::tr("Playlist search");
}
QString SpecialPlaylistType::empty_playlist_text(Playlist* playlist) const {
return QString();
}
QString SpecialPlaylistType::playlist_view_css(Playlist* playlist) const {
return QString();
}
bool SpecialPlaylistType::has_special_search_behaviour(Playlist* playlist) const {
return false;
}
void SpecialPlaylistType::Search(const QString& text, Playlist* playlist) {
}

View File

@ -0,0 +1,48 @@
/* 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 SPECIALPLAYLISTTYPE_H
#define SPECIALPLAYLISTTYPE_H
#include <QIcon>
#include <QString>
class Playlist;
class SpecialPlaylistType {
public:
virtual ~SpecialPlaylistType() {}
virtual QString name() const = 0;
virtual QIcon icon(Playlist* playlist) const;
virtual QString search_hint_text(Playlist* playlist) const;
virtual QString empty_playlist_text(Playlist* playlist) const;
virtual QString playlist_view_css(Playlist* playlist) const;
virtual bool has_special_search_behaviour(Playlist* playlist) const;
virtual void Search(const QString& text, Playlist* playlist);
};
class DefaultPlaylistType : public SpecialPlaylistType {
public:
virtual QString name() const { return QString(); }
};
#endif // SPECIALPLAYLISTTYPE_H

View File

@ -103,7 +103,7 @@ LastFMService::LastFMService(RadioModel* parent)
add_tag_action_->setEnabled(false);
add_custom_action_->setEnabled(false);
model()->player()->AddUrlHandler(url_handler_);
model()->player()->RegisterUrlHandler(url_handler_);
}
LastFMService::~LastFMService() {

View File

@ -93,7 +93,7 @@ MagnatuneService::MagnatuneService(RadioModel* parent)
library_sort_model_->setDynamicSortFilter(true);
library_sort_model_->sort(0);
model()->player()->AddUrlHandler(url_handler_);
model()->player()->RegisterUrlHandler(url_handler_);
}
MagnatuneService::~MagnatuneService() {

View File

@ -44,7 +44,7 @@ SomaFMService::SomaFMService(RadioModel* parent)
get_channels_task_id_(0),
network_(new NetworkAccessManager(this))
{
model()->player()->AddUrlHandler(url_handler_);
model()->player()->RegisterUrlHandler(url_handler_);
}
SomaFMService::~SomaFMService() {

View File

@ -0,0 +1,50 @@
/* 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 "spotifysearchplaylisttype.h"
#include "spotifyservice.h"
const char* SpotifySearchPlaylistType::kName = "spotify-search";
SpotifySearchPlaylistType::SpotifySearchPlaylistType(SpotifyService* service)
: service_(service) {
}
QIcon SpotifySearchPlaylistType::icon(Playlist* playlist) const {
return QIcon(":icons/svg/spotify.svg");
}
QString SpotifySearchPlaylistType::search_hint_text(Playlist* playlist) const {
return QObject::tr("Search Spotify");
}
QString SpotifySearchPlaylistType::empty_playlist_text(Playlist* playlist) const {
return QObject::tr("Start typing in the search box above to find music on Spotify");
}
QString SpotifySearchPlaylistType::playlist_view_css(Playlist* playlist) const {
// TODO
return QString();
}
bool SpotifySearchPlaylistType::has_special_search_behaviour(Playlist* playlist) const {
return true;
}
void SpotifySearchPlaylistType::Search(const QString& text, Playlist* playlist) {
service_->Search(text, playlist);
}

View File

@ -0,0 +1,44 @@
/* 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 SPOTIFYSEARCHPLAYLISTTYPE_H
#define SPOTIFYSEARCHPLAYLISTTYPE_H
#include "playlist/specialplaylisttype.h"
class SpotifyService;
class SpotifySearchPlaylistType : public SpecialPlaylistType {
public:
SpotifySearchPlaylistType(SpotifyService* service);
static const char* kName;
virtual QString name() const { return kName; }
virtual QIcon icon(Playlist* playlist) const;
virtual QString search_hint_text(Playlist* playlist) const;
virtual QString empty_playlist_text(Playlist* playlist) const;
virtual QString playlist_view_css(Playlist* playlist) const;
virtual bool has_special_search_behaviour(Playlist* playlist) const;
virtual void Search(const QString& text, Playlist* playlist);
private:
SpotifyService* service_;
};
#endif // SPOTIFYSEARCHPLAYLISTTYPE_H

View File

@ -1,16 +1,14 @@
#include "radiomodel.h"
#include "spotifyserver.h"
#include "spotifyservice.h"
#include "spotifysearchplaylisttype.h"
#include "spotifyurlhandler.h"
#include "core/database.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "core/player.h"
#include "core/taskmanager.h"
#include "library/library.h"
#include "library/librarybackend.h"
#include "library/libraryfilterwidget.h"
#include "library/librarymodel.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"
#include "spotifyblob/spotifymessagehandler.h"
#include "ui/iconloader.h"
@ -18,12 +16,10 @@
#include <QMenu>
#include <QProcess>
#include <QSettings>
#include <QSortFilterProxyModel>
const char* SpotifyService::kServiceName = "Spotify";
const char* SpotifyService::kSettingsGroup = "Spotify";
const char* SpotifyService::kSearchSongsTable = "spotify_search_songs";
const char* SpotifyService::kSearchFtsTable = "spotify_search_songs_fts";
const int SpotifyService::kSearchDelayMsec = 400;
SpotifyService::SpotifyService(RadioModel* parent)
: RadioService(kServiceName, parent),
@ -31,13 +27,12 @@ SpotifyService::SpotifyService(RadioModel* parent)
url_handler_(new SpotifyUrlHandler(this, this)),
blob_process_(NULL),
root_(NULL),
search_results_(NULL),
starred_(NULL),
inbox_(NULL),
login_task_id_(0),
pending_search_playlist_(NULL),
context_menu_(NULL),
library_filter_(NULL),
library_sort_model_(new QSortFilterProxyModel(this)) {
search_delay_(new QTimer(this)) {
#ifdef Q_OS_DARWIN
blob_path_ = QCoreApplication::applicationDirPath() + "/../Resources/clementine-spotifyblob";
#else
@ -45,18 +40,13 @@ SpotifyService::SpotifyService(RadioModel* parent)
#endif
qLog(Debug) << "Loading spotify blob from:" << blob_path_;
// Create the library backend in the database thread
library_backend_ = new LibraryBackend;
library_backend_->Init(parent->db_thread()->Worker(),
kSearchSongsTable, QString(), QString(), kSearchFtsTable);
library_model_ = new LibraryModel(library_backend_, parent->task_manager(), this);
model()->player()->RegisterUrlHandler(url_handler_);
model()->player()->playlists()->RegisterSpecialPlaylistType(
new SpotifySearchPlaylistType(this));
library_sort_model_->setSourceModel(library_model_);
library_sort_model_->setSortRole(LibraryModel::Role_SortText);
library_sort_model_->setDynamicSortFilter(true);
library_sort_model_->sort(0);
model()->player()->AddUrlHandler(url_handler_);
search_delay_->setInterval(kSearchDelayMsec);
search_delay_->setSingleShot(true);
connect(search_delay_, SIGNAL(timeout()), SLOT(DoSearch()));
}
SpotifyService::~SpotifyService() {
@ -75,8 +65,6 @@ void SpotifyService::LazyPopulate(QStandardItem* item) {
break;
case Type_SearchResults:
library_model_->Init();
model()->merged_model()->AddSubModel(item->index(), library_sort_model_);
break;
case Type_InboxPlaylist:
@ -181,9 +169,9 @@ void SpotifyService::PlaylistsUpdated(const protobuf::Playlists& response) {
// Create starred and inbox playlists if they're not here already
if (!search_results_) {
search_results_ = new QStandardItem(tr("Search results"));
search_results_ = new QStandardItem(IconLoader::Load("edit-find"),
tr("Search Spotify (opens a new tab)"));
search_results_->setData(Type_SearchResults, RadioModel::Role_Type);
search_results_->setData(true, RadioModel::Role_CanLazyLoad);
starred_ = new QStandardItem(QIcon(":/star-on.png"), tr("Starred"));
starred_->setData(Type_StarredPlaylist, RadioModel::Role_Type);
@ -325,30 +313,22 @@ void SpotifyService::EnsureMenuCreated() {
context_menu_->addActions(GetPlaylistActions());
context_menu_->addSeparator();
QAction* config_action = context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Spotify..."), this, SLOT(ShowConfig()));
library_filter_ = new LibraryFilterWidget(0);
library_filter_->SetSettingsGroup(kSettingsGroup);
library_filter_->SetLibraryModel(library_model_);
library_filter_->SetFilterHint(tr("Search Spotify"));
library_filter_->SetAgeFilterEnabled(false);
library_filter_->SetGroupByEnabled(false);
library_filter_->AddMenuAction(config_action);
library_filter_->SetApplyFilterToLibrary(false);
library_filter_->SetDelayBehaviour(LibraryFilterWidget::AlwaysDelayed);
connect(library_filter_, SIGNAL(Filter(QString)), SLOT(Search(QString)));
context_menu_->addAction(IconLoader::Load("edit-find"), tr("Search Spotify (opens a new tab)..."), this, SLOT(OpenSearchTab()));
context_menu_->addSeparator();
context_menu_->addAction(IconLoader::Load("configure"), tr("Configure Spotify..."), this, SLOT(ShowConfig()));
}
QWidget* SpotifyService::HeaderWidget() const {
const_cast<SpotifyService*>(this)->EnsureMenuCreated();
return library_filter_;
void SpotifyService::Search(const QString& text, Playlist* playlist) {
EnsureServerCreated();
pending_search_ = text;
pending_search_playlist_ = playlist;
search_delay_->start();
}
void SpotifyService::Search(const QString& text) {
if (!text.isEmpty()) {
pending_search_ = text;
server_->Search(text, 250);
void SpotifyService::DoSearch() {
if (!pending_search_.isEmpty()) {
server_->Search(pending_search_, 200);
}
}
@ -370,11 +350,21 @@ void SpotifyService::SearchResults(const protobuf::SearchResponse& response) {
qLog(Debug) << "Got" << songs.count() << "results";
library_backend_->DeleteAll();
library_backend_->AddOrUpdateSongs(songs);
pending_search_playlist_->Clear();
pending_search_playlist_->InsertSongs(songs);
}
SpotifyServer* SpotifyService::server() const {
const_cast<SpotifyService*>(this)->EnsureServerCreated();
return server_;
}
void SpotifyService::ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) {
EnsureMenuCreated();
context_menu_->popup(global_pos);
}
void SpotifyService::OpenSearchTab() {
model()->player()->playlists()->New(tr("Search Spotify"), SongList(),
SpotifySearchPlaylistType::kName);
}

View File

@ -10,14 +10,11 @@
#include <boost/shared_ptr.hpp>
class LibraryBackend;
class LibraryModel;
class Playlist;
class SpotifyServer;
class SpotifyUrlHandler;
class QMenu;
class QSortFilterProxyModel;
class QTemporaryFile;
class SpotifyService : public RadioService {
Q_OBJECT
@ -40,17 +37,15 @@ public:
static const char* kServiceName;
static const char* kSettingsGroup;
static const char* kSearchSongsTable;
static const char* kSearchFtsTable;
virtual QStandardItem* CreateRootItem();
virtual void LazyPopulate(QStandardItem* parent);
void Login(const QString& username, const QString& password);
static const int kSearchDelayMsec;
QStandardItem* CreateRootItem();
void LazyPopulate(QStandardItem* parent);
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
PlaylistItem::Options playlistitem_options() const;
QWidget* HeaderWidget() const;
void Login(const QString& username, const QString& password);
void Search(const QString& text, Playlist* playlist);
SpotifyServer* server() const;
@ -79,7 +74,8 @@ private slots:
void UserPlaylistLoaded(const protobuf::LoadPlaylistResponse& response);
void SearchResults(const protobuf::SearchResponse& response);
void Search(const QString& text);
void OpenSearchTab();
void DoSearch();
private:
SpotifyServer* server_;
@ -96,16 +92,12 @@ private:
int login_task_id_;
QString pending_search_;
Playlist* pending_search_playlist_;
QMenu* context_menu_;
QModelIndex context_item_;
QTemporaryFile* database_file_;
boost::shared_ptr<Database> database_;
LibraryBackend* library_backend_;
LibraryFilterWidget* library_filter_;
LibraryModel* library_model_;
QSortFilterProxyModel* library_sort_model_;
QTimer* search_delay_;
};
#endif

View File

@ -12,8 +12,8 @@ public:
PlaylistItemPtr GetItemAt(int pos) const;
PlaylistManagerInterface* playlists() const;
void AddUrlHandler(UrlHandler* handler);
void RemoveUrlHandler(UrlHandler* handler);
void RegisterUrlHandler(UrlHandler* handler);
void UnregisterUrlHandler(UrlHandler* handler);
public slots:
// Manual track change to the specified track

View File

@ -52,7 +52,7 @@ SearchPreview::~SearchPreview() {
void SearchPreview::set_library(LibraryBackend* backend) {
backend_ = backend;
model_ = new Playlist(NULL, NULL, backend_, -1, this);
model_ = new Playlist(NULL, NULL, backend_, -1, QString(), this);
ui_->tree->setModel(model_);
ui_->tree->SetPlaylist(model_);
ui_->tree->SetItemDelegates(backend_);