diff --git a/data/data.qrc b/data/data.qrc
index 3db07f0ae..757b80473 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -314,5 +314,9 @@
schema/schema-30.sql
schema/schema-31.sql
schema/schema-32.sql
+ icons/22x22/edit-find.png
+ icons/32x32/edit-find.png
+ icons/48x48/edit-find.png
+ schema/schema-33.sql
diff --git a/data/icons/22x22/edit-find.png b/data/icons/22x22/edit-find.png
new file mode 100644
index 000000000..1b7a25280
Binary files /dev/null and b/data/icons/22x22/edit-find.png differ
diff --git a/data/icons/32x32/edit-find.png b/data/icons/32x32/edit-find.png
new file mode 100644
index 000000000..9b3fe6bca
Binary files /dev/null and b/data/icons/32x32/edit-find.png differ
diff --git a/data/icons/48x48/edit-find.png b/data/icons/48x48/edit-find.png
new file mode 100644
index 000000000..bb95091ce
Binary files /dev/null and b/data/icons/48x48/edit-find.png differ
diff --git a/data/schema/schema-33.sql b/data/schema/schema-33.sql
new file mode 100644
index 000000000..c73b06163
--- /dev/null
+++ b/data/schema/schema-33.sql
@@ -0,0 +1,3 @@
+ALTER TABLE playlists ADD COLUMN special_type TEXT;
+
+UPDATE schema_version SET version=33;
diff --git a/scripts/digitallyimported-radio/servicebase.py b/scripts/digitallyimported-radio/servicebase.py
index c25fdf6fd..467993ea8 100644
--- a/scripts/digitallyimported-radio/servicebase.py
+++ b/scripts/digitallyimported-radio/servicebase.py
@@ -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__)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c9dd80677..cb229526c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/src/core/database.cpp b/src/core/database.cpp
index 4f5013b2a..dcde7a797 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -32,7 +32,7 @@
#include
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;
diff --git a/src/core/player.cpp b/src/core/player.cpp
index 48c7905af..2e8e7b448 100644
--- a/src/core/player.cpp
+++ b/src/core/player.cpp
@@ -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)),
diff --git a/src/core/player.h b/src/core/player.h
index 7d5839f9d..0b22e5953 100644
--- a/src/core/player.h
+++ b/src/core/player.h
@@ -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();
diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp
index 69dd79eae..4304a2773 100644
--- a/src/playlist/playlist.cpp
+++ b/src/playlist/playlist.cpp
@@ -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);
diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h
index b9f645aa8..054563fab 100644
--- a/src/playlist/playlist.h
+++ b/src/playlist/playlist.h
@@ -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 veto_listeners_;
+
+ QString special_type_;
};
QDataStream& operator <<(QDataStream&, const Playlist*);
diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp
index 1da313dda..651fba7c1 100644
--- a/src/playlist/playlistbackend.cpp
+++ b/src/playlist/playlistbackend.cpp
@@ -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;
diff --git a/src/playlist/playlistbackend.h b/src/playlist/playlistbackend.h
index 0f3bea83b..9e4d94cfe 100644
--- a/src/playlist/playlistbackend.h
+++ b/src/playlist/playlistbackend.h
@@ -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 PlaylistList;
typedef QFuture PlaylistItemFuture;
@@ -58,7 +62,7 @@ class PlaylistBackend : public QObject {
void SetPlaylistOrder(const QList& 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);
diff --git a/src/playlist/playlistcontainer.cpp b/src/playlist/playlistcontainer.cpp
index e131dacac..ea381d514 100644
--- a/src/playlist/playlistcontainer.cpp
+++ b/src/playlist/playlistcontainer.cpp
@@ -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) {
diff --git a/src/playlist/playlistcontainer.ui b/src/playlist/playlistcontainer.ui
index 90ea4d5fc..842961805 100644
--- a/src/playlist/playlistcontainer.ui
+++ b/src/playlist/playlistcontainer.ui
@@ -114,7 +114,7 @@
-
- Playlist search
+
diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp
index 66de5c974..0e6ba4f83 100644
--- a/src/playlist/playlistmanager.cpp
+++ b/src/playlist/playlistmanager.cpp
@@ -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_;
+}
diff --git a/src/playlist/playlistmanager.h b/src/playlist/playlistmanager.h
index fe4847eba..519a7dfa8 100644
--- a/src/playlist/playlistmanager.h
+++ b/src/playlist/playlistmanager.h
@@ -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 playlists_;
+ QMap special_playlist_types_;
+ SpecialPlaylistType* default_playlist_type_;
+
int current_;
int active_;
};
diff --git a/src/playlist/playlisttabbar.cpp b/src/playlist/playlisttabbar.cpp
index a62d9b7ac..8575e8c67 100644
--- a/src/playlist/playlisttabbar.cpp
+++ b/src/playlist/playlisttabbar.cpp
@@ -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)
diff --git a/src/playlist/playlisttabbar.h b/src/playlist/playlisttabbar.h
index fb84f7f60..2a20b9130 100644
--- a/src/playlist/playlisttabbar.h
+++ b/src/playlist/playlisttabbar.h
@@ -19,6 +19,7 @@
#define PLAYLISTTABBAR_H
#include
+#include
#include
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);
diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp
index 9c8a89f0a..e93f68677 100644
--- a/src/playlist/playlistview.cpp
+++ b/src/playlist/playlistview.cpp
@@ -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
#include
diff --git a/src/playlist/specialplaylisttype.cpp b/src/playlist/specialplaylisttype.cpp
new file mode 100644
index 000000000..ef4e026aa
--- /dev/null
+++ b/src/playlist/specialplaylisttype.cpp
@@ -0,0 +1,43 @@
+/* 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 "specialplaylisttype.h"
+
+#include
+
+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) {
+}
diff --git a/src/playlist/specialplaylisttype.h b/src/playlist/specialplaylisttype.h
new file mode 100644
index 000000000..a3278ed94
--- /dev/null
+++ b/src/playlist/specialplaylisttype.h
@@ -0,0 +1,48 @@
+/* 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 SPECIALPLAYLISTTYPE_H
+#define SPECIALPLAYLISTTYPE_H
+
+#include
+#include
+
+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
diff --git a/src/radio/lastfmservice.cpp b/src/radio/lastfmservice.cpp
index 2d8eced86..b2b9c786a 100644
--- a/src/radio/lastfmservice.cpp
+++ b/src/radio/lastfmservice.cpp
@@ -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() {
diff --git a/src/radio/magnatuneservice.cpp b/src/radio/magnatuneservice.cpp
index bf5675b52..a8ef32af8 100644
--- a/src/radio/magnatuneservice.cpp
+++ b/src/radio/magnatuneservice.cpp
@@ -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() {
diff --git a/src/radio/somafmservice.cpp b/src/radio/somafmservice.cpp
index 652dfee8a..b49294327 100644
--- a/src/radio/somafmservice.cpp
+++ b/src/radio/somafmservice.cpp
@@ -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() {
diff --git a/src/radio/spotifysearchplaylisttype.cpp b/src/radio/spotifysearchplaylisttype.cpp
new file mode 100644
index 000000000..97cc675e5
--- /dev/null
+++ b/src/radio/spotifysearchplaylisttype.cpp
@@ -0,0 +1,50 @@
+/* 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 "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);
+}
diff --git a/src/radio/spotifysearchplaylisttype.h b/src/radio/spotifysearchplaylisttype.h
new file mode 100644
index 000000000..6dae48b46
--- /dev/null
+++ b/src/radio/spotifysearchplaylisttype.h
@@ -0,0 +1,44 @@
+/* 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 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
diff --git a/src/radio/spotifyservice.cpp b/src/radio/spotifyservice.cpp
index 4835b6930..ddda4bb47 100644
--- a/src/radio/spotifyservice.cpp
+++ b/src/radio/spotifyservice.cpp
@@ -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
#include
#include
-#include
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(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(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);
+}
diff --git a/src/radio/spotifyservice.h b/src/radio/spotifyservice.h
index b4f233488..1712742a4 100644
--- a/src/radio/spotifyservice.h
+++ b/src/radio/spotifyservice.h
@@ -10,14 +10,11 @@
#include
-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_;
- LibraryBackend* library_backend_;
- LibraryFilterWidget* library_filter_;
- LibraryModel* library_model_;
- QSortFilterProxyModel* library_sort_model_;
+ QTimer* search_delay_;
};
#endif
diff --git a/src/scripting/python/player.sip b/src/scripting/python/player.sip
index aa5136601..2e9b3931b 100644
--- a/src/scripting/python/player.sip
+++ b/src/scripting/python/player.sip
@@ -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
diff --git a/src/smartplaylists/searchpreview.cpp b/src/smartplaylists/searchpreview.cpp
index adc92194d..d638cb00e 100644
--- a/src/smartplaylists/searchpreview.cpp
+++ b/src/smartplaylists/searchpreview.cpp
@@ -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_);