mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
Friend and neighbour radio
This commit is contained in:
parent
92db175819
commit
56e685bb3b
@ -41,5 +41,11 @@
|
||||
<file>spinner.gif</file>
|
||||
<file>last.fm/ban.png</file>
|
||||
<file>last.fm/love.png</file>
|
||||
<file>last.fm/icon_radio.png</file>
|
||||
<file>last.fm/icon_tag.png</file>
|
||||
<file>last.fm/icon_user.png</file>
|
||||
<file>last.fm/my_friends.png</file>
|
||||
<file>last.fm/my_neighbours.png</file>
|
||||
<file>last.fm/user_purple.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/last.fm/icon_radio.png
Normal file
BIN
data/last.fm/icon_radio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 398 B |
BIN
data/last.fm/icon_tag.png
Normal file
BIN
data/last.fm/icon_tag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 600 B |
BIN
data/last.fm/icon_user.png
Normal file
BIN
data/last.fm/icon_user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 729 B |
BIN
data/last.fm/my_friends.png
Normal file
BIN
data/last.fm/my_friends.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
data/last.fm/my_neighbours.png
Normal file
BIN
data/last.fm/my_neighbours.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
data/last.fm/user_purple.png
Normal file
BIN
data/last.fm/user_purple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 798 B |
@ -23,7 +23,9 @@ LastFMService::LastFMService(QObject* parent)
|
||||
scrobbler_(NULL),
|
||||
context_menu_(new QMenu),
|
||||
initial_tune_(false),
|
||||
scrobbling_enabled_(false)
|
||||
scrobbling_enabled_(false),
|
||||
friends_list_(NULL),
|
||||
neighbours_list_(NULL)
|
||||
{
|
||||
lastfm::ws::ApiKey = kApiKey;
|
||||
lastfm::ws::SharedSecret = kSecret;
|
||||
@ -70,9 +72,11 @@ RadioItem* LastFMService::CreateRootItem(RadioItem* parent) {
|
||||
}
|
||||
|
||||
void LastFMService::LazyPopulate(RadioItem *item) {
|
||||
RadioItem* c = NULL;
|
||||
|
||||
switch (item->type) {
|
||||
case RadioItem::Type_Service:
|
||||
// Create child items
|
||||
// Normal radio types
|
||||
CreateStationItem(Type_MyRecommendations, "My Recommendations",
|
||||
":last.fm/recommended_radio.png", item);
|
||||
CreateStationItem(Type_MyRadio, "My Radio Station",
|
||||
@ -82,10 +86,34 @@ void LastFMService::LazyPopulate(RadioItem *item) {
|
||||
CreateStationItem(Type_MyNeighbourhood, "My Neighbourhood",
|
||||
":last.fm/neighbour_radio.png", item);
|
||||
|
||||
// Types that spawn a popup dialog
|
||||
c = CreateStationItem(Type_ArtistRadio, "Artist radio...",
|
||||
":last.fm/icon_radio.png", item);
|
||||
c->playable = false;
|
||||
|
||||
c = CreateStationItem(Type_TagRadio, "Tag radio...",
|
||||
":last.fm/icon_tag.png", item);
|
||||
c->playable = false;
|
||||
|
||||
// Types that have children
|
||||
friends_list_ = new RadioItem(this, Type_MyFriends, "Friends", item);
|
||||
friends_list_->icon = QIcon(":last.fm/my_friends.png");
|
||||
|
||||
neighbours_list_ = new RadioItem(this, Type_MyNeighbours, "Neighbours", item);
|
||||
neighbours_list_->icon = QIcon(":last.fm/my_neighbours.png");
|
||||
|
||||
if (!IsAuthenticated())
|
||||
config_->show();
|
||||
break;
|
||||
|
||||
case Type_MyFriends:
|
||||
RefreshFriends();
|
||||
break;
|
||||
|
||||
case Type_MyNeighbours:
|
||||
RefreshNeighbours();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -158,18 +186,26 @@ QUrl LastFMService::UrlForItem(const RadioItem* item) const {
|
||||
|
||||
case Type_MyRadio:
|
||||
return "lastfm://user/" + lastfm::ws::Username + "/library";
|
||||
|
||||
case Type_FriendRadio:
|
||||
return "lastfm://user/" + item->key + "/library";
|
||||
|
||||
case Type_NeighbourRadio:
|
||||
return "lastfm://user/" + item->key + "/library";
|
||||
}
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
QString LastFMService::TitleForItem(const RadioItem* item) const {
|
||||
const QString user(lastfm::ws::Username);
|
||||
const QString me(lastfm::ws::Username);
|
||||
|
||||
switch (item->type) {
|
||||
case Type_MyRecommendations: return user + "'s Recommended Radio";
|
||||
case Type_MyLoved: return user + "'s Loved Tracks";
|
||||
case Type_MyNeighbourhood: return user + "'s Neighbour Radio";
|
||||
case Type_MyRadio: return user + "'s Library";
|
||||
case Type_MyRecommendations: return me + "'s Recommended Radio";
|
||||
case Type_MyLoved: return me + "'s Loved Tracks";
|
||||
case Type_MyNeighbourhood: return me + "'s Neighbour Radio";
|
||||
case Type_MyRadio: return me + "'s Library";
|
||||
case Type_FriendRadio: return item->key + "'s Library";
|
||||
case Type_NeighbourRadio: return item->key + "'s Library";
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@ -321,3 +357,71 @@ void LastFMService::Ban() {
|
||||
void LastFMService::ShowContextMenu(RadioItem *, const QPoint &global_pos) {
|
||||
context_menu_->popup(global_pos);
|
||||
}
|
||||
|
||||
void LastFMService::RefreshFriends() {
|
||||
if (!friends_list_ || !IsAuthenticated())
|
||||
return;
|
||||
|
||||
friends_list_->ClearNotify();
|
||||
|
||||
lastfm::AuthenticatedUser user;
|
||||
QNetworkReply* reply = user.getFriends();
|
||||
connect(reply, SIGNAL(finished()), SLOT(RefreshFriendsFinished()));
|
||||
}
|
||||
|
||||
void LastFMService::RefreshNeighbours() {
|
||||
if (!friends_list_ || !IsAuthenticated())
|
||||
return;
|
||||
|
||||
neighbours_list_->ClearNotify();
|
||||
|
||||
lastfm::AuthenticatedUser user;
|
||||
QNetworkReply* reply = user.getNeighbours();
|
||||
connect(reply, SIGNAL(finished()), SLOT(RefreshNeighboursFinished()));
|
||||
}
|
||||
|
||||
void LastFMService::RefreshFriendsFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
QList<lastfm::User> friends;
|
||||
|
||||
try {
|
||||
friends = lastfm::User::list(reply);
|
||||
} catch (std::runtime_error& e) {
|
||||
qDebug() << e.what();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const lastfm::User& f, friends) {
|
||||
RadioItem* item = new RadioItem(this, Type_FriendRadio, f);
|
||||
item->icon = QIcon(":last.fm/icon_user.png");
|
||||
item->playable = true;
|
||||
item->lazy_loaded = true;
|
||||
item->InsertNotify(friends_list_);
|
||||
}
|
||||
}
|
||||
|
||||
void LastFMService::RefreshNeighboursFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
QList<lastfm::User> neighbours;
|
||||
|
||||
try {
|
||||
neighbours = lastfm::User::list(reply);
|
||||
} catch (std::runtime_error& e) {
|
||||
qDebug() << e.what();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const lastfm::User& n, neighbours) {
|
||||
RadioItem* item = new RadioItem(this, Type_NeighbourRadio, n);
|
||||
item->icon = QIcon(":last.fm/user_purple.png");
|
||||
item->playable = true;
|
||||
item->lazy_loaded = true;
|
||||
item->InsertNotify(neighbours_list_);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,12 @@ class LastFMService : public RadioService {
|
||||
Type_MyRadio,
|
||||
Type_MyLoved,
|
||||
Type_MyNeighbourhood,
|
||||
Type_ArtistRadio,
|
||||
Type_TagRadio,
|
||||
Type_MyFriends,
|
||||
Type_MyNeighbours,
|
||||
Type_FriendRadio,
|
||||
Type_NeighbourRadio,
|
||||
};
|
||||
|
||||
// RadioService
|
||||
@ -48,11 +54,11 @@ class LastFMService : public RadioService {
|
||||
// Last.fm specific stuff
|
||||
bool IsAuthenticated() const;
|
||||
bool IsScrobblingEnabled() const { return scrobbling_enabled_; }
|
||||
|
||||
void Authenticate(const QString& username, const QString& password);
|
||||
|
||||
void NowPlaying(const Song& song);
|
||||
|
||||
public slots:
|
||||
void NowPlaying(const Song& song);
|
||||
void Scrobble();
|
||||
void Love();
|
||||
void Ban();
|
||||
@ -64,6 +70,8 @@ class LastFMService : public RadioService {
|
||||
private slots:
|
||||
void AuthenticateReplyFinished();
|
||||
void ScrobblingEnabledChangedSlot(bool value);
|
||||
void RefreshFriendsFinished();
|
||||
void RefreshNeighboursFinished();
|
||||
|
||||
void TunerTrackAvailable();
|
||||
void TunerError(lastfm::ws::Error error);
|
||||
@ -74,6 +82,8 @@ class LastFMService : public RadioService {
|
||||
QString ErrorString(lastfm::ws::Error error) const;
|
||||
bool InitScrobbler();
|
||||
lastfm::Track TrackFromSong(const Song& song) const;
|
||||
void RefreshFriends();
|
||||
void RefreshNeighbours();
|
||||
|
||||
private:
|
||||
lastfm::RadioTuner* tuner_;
|
||||
@ -87,6 +97,9 @@ class LastFMService : public RadioService {
|
||||
bool initial_tune_;
|
||||
|
||||
bool scrobbling_enabled_;
|
||||
|
||||
RadioItem* friends_list_;
|
||||
RadioItem* neighbours_list_;
|
||||
};
|
||||
|
||||
#endif // LASTFMSERVICE_H
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
Library::Library(EngineBase* engine, QObject* parent)
|
||||
: SimpleTreeModel<LibraryItem>(new LibraryItem(LibraryItem::Type_Root), parent),
|
||||
: SimpleTreeModel<LibraryItem>(new LibraryItem(this), parent),
|
||||
engine_(engine),
|
||||
backend_(new BackgroundThread<LibraryBackend>(this)),
|
||||
watcher_(new BackgroundThread<LibraryWatcher>(this)),
|
||||
@ -357,7 +357,7 @@ void Library::Reset() {
|
||||
divider_nodes_.clear();
|
||||
compilation_artist_node_ = NULL;
|
||||
|
||||
root_ = new LibraryItem(LibraryItem::Type_Root);
|
||||
root_ = new LibraryItem(this);
|
||||
root_->lazy_loaded = true;
|
||||
|
||||
// Various artists?
|
||||
|
@ -19,6 +19,8 @@ class LibraryItem : public SimpleTreeItem<LibraryItem> {
|
||||
Type_Song,
|
||||
};
|
||||
|
||||
LibraryItem(SimpleTreeModel<LibraryItem>* model)
|
||||
: SimpleTreeItem<LibraryItem>(Type_Root, model) {}
|
||||
LibraryItem(Type type, const QString& key = QString::null, LibraryItem* parent = NULL)
|
||||
: SimpleTreeItem<LibraryItem>(type, key, parent) {}
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
#include "radioitem.h"
|
||||
#include "radioservice.h"
|
||||
|
||||
RadioItem::RadioItem(SimpleTreeModel<RadioItem> *model)
|
||||
: SimpleTreeItem<RadioItem>(Type_Root, model),
|
||||
service(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
RadioItem::RadioItem(RadioService* _service, int type, const QString& key,
|
||||
RadioItem* parent)
|
||||
: SimpleTreeItem<RadioItem>(type, key, parent),
|
||||
|
@ -15,6 +15,7 @@ class RadioItem : public SimpleTreeItem<RadioItem> {
|
||||
Type_Service,
|
||||
};
|
||||
|
||||
RadioItem(SimpleTreeModel<RadioItem>* model);
|
||||
RadioItem(RadioService* _service, int type, const QString& key = QString::null,
|
||||
RadioItem* parent = NULL);
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
QMap<QString, RadioService*> RadioModel::sServices;
|
||||
|
||||
RadioModel::RadioModel(QObject* parent)
|
||||
: SimpleTreeModel<RadioItem>(new RadioItem(NULL, RadioItem::Type_Root), parent)
|
||||
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent)
|
||||
{
|
||||
Q_ASSERT(sServices.isEmpty());
|
||||
|
||||
|
@ -1,15 +1,22 @@
|
||||
#ifndef SIMPLETREEITEM_H
|
||||
#define SIMPLETREEITEM_H
|
||||
|
||||
#include "simpletreemodel.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
template <typename T>
|
||||
class SimpleTreeItem {
|
||||
public:
|
||||
SimpleTreeItem(int _type, SimpleTreeModel<T>* _model); // For the root item
|
||||
SimpleTreeItem(int _type, const QString& _key = QString::null, T* _parent = NULL);
|
||||
virtual ~SimpleTreeItem();
|
||||
|
||||
void InsertNotify(T* _parent);
|
||||
void DeleteNotify(int child_row);
|
||||
void ClearNotify();
|
||||
|
||||
void Delete(int child_row);
|
||||
T* ChildByKey(const QString& key) const;
|
||||
|
||||
@ -26,23 +33,67 @@ class SimpleTreeItem {
|
||||
|
||||
T* parent;
|
||||
QList<T*> children;
|
||||
|
||||
SimpleTreeModel<T>* model;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
|
||||
: type(_type),
|
||||
row(0),
|
||||
lazy_loaded(true),
|
||||
parent(NULL),
|
||||
model(_model)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
|
||||
: type(_type),
|
||||
key(_key),
|
||||
lazy_loaded(false),
|
||||
parent(_parent)
|
||||
parent(_parent),
|
||||
model(_parent ? _parent->model : NULL)
|
||||
{
|
||||
if (parent) {
|
||||
row = parent->children.count();
|
||||
parent->children << static_cast<T*>(this);
|
||||
} else {
|
||||
row = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::InsertNotify(T* _parent) {
|
||||
parent = _parent;
|
||||
model = parent->model;
|
||||
row = parent->children.count();
|
||||
|
||||
model->BeginInsert(parent, row);
|
||||
parent->children << static_cast<T*>(this);
|
||||
model->EndInsert();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::DeleteNotify(int child_row) {
|
||||
model->BeginDelete(static_cast<T*>(this), child_row);
|
||||
delete children.takeAt(child_row);
|
||||
|
||||
// Adjust row numbers of those below it :(
|
||||
for (int i=child_row ; i<children.count() ; ++i)
|
||||
children[i]->row --;
|
||||
model->EndDelete();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeItem<T>::ClearNotify() {
|
||||
model->BeginDelete(static_cast<T*>(this), 0, children.count()-1);
|
||||
|
||||
qDeleteAll(children);
|
||||
children.clear();
|
||||
|
||||
model->EndDelete();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SimpleTreeItem<T>::~SimpleTreeItem() {
|
||||
qDeleteAll(children);
|
||||
|
@ -21,6 +21,12 @@ class SimpleTreeModel : public QAbstractItemModel {
|
||||
T* IndexToItem(const QModelIndex& index) const;
|
||||
QModelIndex ItemToIndex(T* item) const;
|
||||
|
||||
// Called by items
|
||||
void BeginInsert(T* parent, int start, int end = -1);
|
||||
void EndInsert();
|
||||
void BeginDelete(T* parent, int start, int end = -1);
|
||||
void EndDelete();
|
||||
|
||||
protected:
|
||||
virtual void LazyPopulate(T* item) = 0;
|
||||
|
||||
@ -87,4 +93,26 @@ bool SimpleTreeModel<T>::hasChildren(const QModelIndex &parent) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::BeginInsert(T* parent, int start, int end) {
|
||||
if (end == -1) end = start;
|
||||
beginInsertRows(ItemToIndex(parent), start, end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::EndInsert() {
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::BeginDelete(T* parent, int start, int end) {
|
||||
if (end == -1) end = start;
|
||||
beginRemoveRows(ItemToIndex(parent), start, end);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SimpleTreeModel<T>::EndDelete() {
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
#endif // SIMPLETREEMODEL_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user