Show spotify search results in a separate playlist tab rather than in a tree in the sidebar.
This commit is contained in:
parent
02e2a84e70
commit
06852aaeb7
|
@ -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 |
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE playlists ADD COLUMN special_type TEXT;
|
||||
|
||||
UPDATE schema_version SET version=33;
|
|
@ -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__)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
Playlist* playlist = manager_->current();
|
||||
SpecialPlaylistType* type = manager_->GetPlaylistType(playlist->special_type());
|
||||
|
||||
if (type->has_special_search_behaviour(playlist)) {
|
||||
type->Search(filter_->text(), playlist);
|
||||
} else {
|
||||
manager_->current()->proxy()->setFilterFixedString(filter_->text());
|
||||
ui_->playlist->JumpToCurrentlyPlayingTrack();
|
||||
|
||||
bool no_matches = manager_->current()->proxy()->rowCount() == 0 &&
|
||||
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) {
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
<item>
|
||||
<widget class="LineEdit" name="filter">
|
||||
<property name="hint" stdset="0">
|
||||
<string>Playlist search</string>
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
}
|
|
@ -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
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
|
||||
void SpotifyService::Search(const QString& text) {
|
||||
if (!text.isEmpty()) {
|
||||
pending_search_ = text;
|
||||
server_->Search(text, 250);
|
||||
pending_search_playlist_ = playlist;
|
||||
search_delay_->start();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_);
|
||||
|
|
Loading…
Reference in New Issue