Show icons for each biography, order biographies by relevance, don't refresh artist info when switching to another song by the same artist.

This commit is contained in:
David Sansome 2010-10-09 12:39:49 +00:00
parent 95b007403e
commit 7e39bfece2
30 changed files with 182 additions and 58 deletions

View File

@ -32,7 +32,7 @@
<file>last.fm/my_friends.png</file>
<file>last.fm/my_neighbours.png</file>
<file>last.fm/user_purple.png</file>
<file>somafm.png</file>
<file>providers/somafm.png</file>
<file>schema-1.sql</file>
<file>schema-2.sql</file>
<file>nocover.png</file>
@ -45,7 +45,7 @@
<file>schema-7.sql</file>
<file>tiny-pause.png</file>
<file>tiny-start.png</file>
<file>magnatune.png</file>
<file>providers/magnatune.png</file>
<file>schema-8.sql</file>
<file>schema-9.sql</file>
<file>icons/22x22/application-exit.png</file>
@ -266,5 +266,12 @@
<file>schema-17.sql</file>
<file>lyrics/ultimate_providers.xml</file>
<file>sidebar_background.png</file>
<file>providers/wikipedia.png</file>
<file>providers/aol.png</file>
<file>providers/amazon.png</file>
<file>providers/myspace.png</file>
<file>providers/mog.png</file>
<file>providers/mtvmusic.png</file>
<file>providers/cdbaby.png</file>
</qresource>
</RCC>

BIN
data/providers/amazon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
data/providers/aol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
data/providers/cdbaby.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
data/providers/mog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

BIN
data/providers/mtvmusic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

BIN
data/providers/myspace.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 560 B

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -15,7 +15,7 @@
</property>
<property name="windowIcon">
<iconset resource="../../data/data.qrc">
<normaloff>:/magnatune.png</normaloff>:/magnatune.png</iconset>
<normaloff>:/providers/magnatune.png</normaloff>:/providers/magnatune.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@ -39,9 +39,6 @@
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>

View File

@ -106,7 +106,7 @@ void MagnatuneService::ReloadSettings() {
RadioItem* MagnatuneService::CreateRootItem(RadioItem *parent) {
root_ = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
root_->icon = QIcon(":magnatune.png");
root_->icon = QIcon(":/providers/magnatune.png");
return root_;
}

View File

@ -51,7 +51,7 @@ SomaFMService::~SomaFMService() {
RadioItem* SomaFMService::CreateRootItem(RadioItem* parent) {
root_ = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
root_->icon = QIcon(":somafm.png");
root_->icon = QIcon(":/providers/somafm.png");
return root_;
}

View File

@ -27,7 +27,7 @@ ArtistInfoFetcher::ArtistInfoFetcher(QObject* parent)
void ArtistInfoFetcher::AddProvider(ArtistInfoProvider* provider) {
providers_ << provider;
connect(provider, SIGNAL(ImageReady(int,QUrl)), SIGNAL(ImageReady(int,QUrl)));
connect(provider, SIGNAL(InfoReady(int,QString,QWidget*)), SIGNAL(InfoReady(int,QString,QWidget*)));
connect(provider, SIGNAL(InfoReady(int,CollapsibleInfoPane::Data)), SIGNAL(InfoReady(int,CollapsibleInfoPane::Data)));
}
int ArtistInfoFetcher::FetchInfo(const QString& artist) {

View File

@ -20,6 +20,8 @@
#include <QObject>
#include <QUrl>
#include "collapsibleinfopane.h"
class ArtistInfoProvider;
class ArtistInfoFetcher : public QObject {
@ -32,7 +34,7 @@ public:
signals:
void ImageReady(int id, const QUrl& url);
void InfoReady(int id, const QString& title, QWidget* widget);
void InfoReady(int id, const CollapsibleInfoPane::Data&);
private:
void AddProvider(ArtistInfoProvider* provider);

View File

@ -20,6 +20,8 @@
#include <QObject>
#include <QUrl>
#include "collapsibleinfopane.h"
class ArtistInfoProvider : public QObject {
Q_OBJECT
@ -30,7 +32,7 @@ public:
signals:
void ImageReady(int id, const QUrl& url);
void InfoReady(int id, const QString& title, QWidget* widget);
void InfoReady(int id, const CollapsibleInfoPane::Data& data);
};
#endif // ARTISTINFOPROVIDER_H

View File

@ -30,10 +30,12 @@ ArtistInfoView::ArtistInfoView(NetworkAccessManager* network, QWidget *parent)
fetcher_(new ArtistInfoFetcher(this)),
current_request_id_(-1),
scroll_area_(new QScrollArea),
container_(new QVBoxLayout)
container_(new QVBoxLayout),
image_view_(NULL),
section_container_(NULL)
{
connect(fetcher_, SIGNAL(ImageReady(int,QUrl)), SLOT(ImageReady(int,QUrl)));
connect(fetcher_, SIGNAL(InfoReady(int,QString,QWidget*)), SLOT(InfoReady(int,QString,QWidget*)));
connect(fetcher_, SIGNAL(InfoReady(int,CollapsibleInfoPane::Data)), SLOT(InfoReady(int,CollapsibleInfoPane::Data)));
// Add the top-level scroll area
setLayout(new QVBoxLayout);
@ -47,6 +49,7 @@ ArtistInfoView::ArtistInfoView(NetworkAccessManager* network, QWidget *parent)
container_widget->setBackgroundRole(QPalette::Base);
container_->setSizeConstraint(QLayout::SetMinAndMaxSize);
container_->setContentsMargins(0, 0, 0, 0);
container_->setSpacing(0);
scroll_area_->setWidget(container_widget);
scroll_area_->setWidgetResizable(true);
@ -62,23 +65,43 @@ ArtistInfoView::ArtistInfoView(NetworkAccessManager* network, QWidget *parent)
ArtistInfoView::~ArtistInfoView() {
}
void ArtistInfoView::AddChild(QWidget* widget) {
children_ << widget;
container_->insertWidget(container_->count() - 1, widget);
widget->show();
void ArtistInfoView::AddSection(CollapsibleInfoPane* section) {
int index = 0;
for ( ; index<sections_.count() ; ++index) {
if (section->data() < sections_[index]->data())
break;
}
sections_.insert(index, section);
qobject_cast<QVBoxLayout*>(section_container_->layout())->insertWidget(index, section);
section->show();
}
void ArtistInfoView::Clear() {
qDeleteAll(children_);
children_.clear();
delete image_view_;
delete section_container_;
sections_.clear();
}
bool ArtistInfoView::NeedsUpdate(const Song& old_metadata, const Song& new_metadata) const {
return old_metadata.artist() != new_metadata.artist();
}
void ArtistInfoView::Update(const Song& metadata) {
Clear();
current_request_id_ = fetcher_->FetchInfo(metadata.artist());
// Image view goes at the top
image_view_ = new PrettyImageView(network_);
AddChild(image_view_);
container_->insertWidget(0, image_view_);
// Container for collapsable sections goes below
section_container_ = new QWidget;
section_container_->setLayout(new QVBoxLayout);
section_container_->layout()->setContentsMargins(0, 0, 0, 0);
section_container_->layout()->setSpacing(1);
section_container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
container_->insertWidget(1, section_container_);
}
void ArtistInfoView::ImageReady(int id, const QUrl& url) {
@ -88,14 +111,11 @@ void ArtistInfoView::ImageReady(int id, const QUrl& url) {
image_view_->AddImage(url);
}
void ArtistInfoView::InfoReady(int id, const QString& title, QWidget* widget) {
void ArtistInfoView::InfoReady(int id, const CollapsibleInfoPane::Data& data) {
if (id != current_request_id_) {
delete widget;
delete data.contents_;
return;
}
CollapsibleInfoPane* pane = new CollapsibleInfoPane(this);
pane->SetTitle(title);
pane->SetWidget(widget);
AddChild(pane);
AddSection(new CollapsibleInfoPane(data, this));
}

View File

@ -17,6 +17,7 @@
#ifndef ARTISTINFOVIEW_H
#define ARTISTINFOVIEW_H
#include "collapsibleinfopane.h"
#include "songinfobase.h"
class ArtistInfoFetcher;
@ -34,14 +35,15 @@ public:
protected:
void Update(const Song& metadata);
bool NeedsUpdate(const Song& old_metadata, const Song& new_metadata) const;
private:
void AddChild(QWidget* widget);
void AddSection(CollapsibleInfoPane* section);
void Clear();
private slots:
void ImageReady(int id, const QUrl& url);
void InfoReady(int id, const QString& title, QWidget* widget);
void InfoReady(int id, const CollapsibleInfoPane::Data& data);
private:
ArtistInfoFetcher* fetcher_;
@ -51,7 +53,8 @@ private:
QVBoxLayout* container_;
PrettyImageView* image_view_;
QList<QWidget*> children_;
QWidget* section_container_;
QList<CollapsibleInfoPane*> sections_;
};
#endif // ARTISTINFOVIEW_H

View File

@ -22,6 +22,7 @@
#include <QStyleOption>
const int CollapsibleInfoHeader::kHeight = 20;
const int CollapsibleInfoHeader::kIconSize = 16;
CollapsibleInfoHeader::CollapsibleInfoHeader(QWidget* parent)
: QWidget(parent),
@ -38,6 +39,11 @@ void CollapsibleInfoHeader::SetTitle(const QString& title) {
update();
}
void CollapsibleInfoHeader::SetIcon(const QIcon& icon) {
icon_ = icon;
update();
}
void CollapsibleInfoHeader::SetExpanded(bool expanded) {
expanded_ = expanded;
@ -62,8 +68,9 @@ void CollapsibleInfoHeader::paintEvent(QPaintEvent* e) {
QPainter p(this);
QRect indicator_rect(0, 0, height(), height());
QRect icon_rect(height() + 2, (kHeight - kIconSize) / 2, kIconSize, kIconSize);
QRect text_rect(rect());
text_rect.setLeft(height() + 6);
text_rect.setLeft(icon_rect.right() + 4);
// Draw the background
const QColor bg_color_1(palette().color(QPalette::Highlight).lighter(120));
@ -96,6 +103,9 @@ void CollapsibleInfoHeader::paintEvent(QPaintEvent* e) {
// will trigger QStyleSheetStyle's recursion guard (I don't know why).
QApplication::style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, &p, this);
// Draw the icon
p.drawPixmap(icon_rect, icon_.pixmap(kIconSize));
// Draw the title text
QFont bold_font(font());
bold_font.setBold(true);

View File

@ -17,6 +17,7 @@
#ifndef COLLAPSIBLEINFOHEADER_H
#define COLLAPSIBLEINFOHEADER_H
#include <QIcon>
#include <QWidget>
class CollapsibleInfoHeader : public QWidget {
@ -26,14 +27,17 @@ public:
CollapsibleInfoHeader(QWidget* parent = 0);
static const int kHeight;
static const int kIconSize;
bool expanded() const { return expanded_; }
bool hovering() const { return hovering_; }
const QString& title() const { return title_; }
const QIcon& icon() const { return icon_; }
public slots:
void SetExpanded(bool expanded);
void SetTitle(const QString& title);
void SetIcon(const QIcon& icon);
signals:
void Expanded();
@ -50,6 +54,7 @@ private:
bool expanded_;
bool hovering_;
QString title_;
QIcon icon_;
};
#endif // COLLAPSIBLEINFOHEADER_H

View File

@ -19,10 +19,10 @@
#include <QVBoxLayout>
CollapsibleInfoPane::CollapsibleInfoPane(QWidget* parent)
CollapsibleInfoPane::CollapsibleInfoPane(const Data& data, QWidget* parent)
: QWidget(parent),
header_(new CollapsibleInfoHeader(this)),
widget_(NULL)
data_(data),
header_(new CollapsibleInfoHeader(this))
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
@ -31,24 +31,16 @@ CollapsibleInfoPane::CollapsibleInfoPane(QWidget* parent)
setLayout(layout);
layout->addWidget(header_);
layout->addWidget(data.contents_);
header_->SetTitle(data.title_);
header_->SetIcon(data.icon_);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
connect(header_, SIGNAL(ExpandedToggled(bool)), SLOT(ExpandedToggled(bool)));
}
void CollapsibleInfoPane::SetTitle(const QString& title) {
header_->SetTitle(title);
}
void CollapsibleInfoPane::SetWidget(QWidget* widget) {
if (widget_)
delete widget_;
widget_ = widget;
layout()->addWidget(widget);
}
void CollapsibleInfoPane::Collapse() {
header_->SetExpanded(false);
}
@ -58,5 +50,12 @@ void CollapsibleInfoPane::Expand() {
}
void CollapsibleInfoPane::ExpandedToggled(bool expanded) {
widget_->setVisible(expanded);
data_.contents_->setVisible(expanded);
}
bool CollapsibleInfoPane::Data::operator <(const CollapsibleInfoPane::Data& other) const {
const int my_score = type_ * 1000 + relevance_;
const int other_score = other.type_ * 1000 + other.relevance_;
return my_score > other_score;
}

View File

@ -17,6 +17,7 @@
#ifndef COLLAPSIBLEINFOPANE_H
#define COLLAPSIBLEINFOPANE_H
#include <QIcon>
#include <QWidget>
class CollapsibleInfoHeader;
@ -25,10 +26,28 @@ class CollapsibleInfoPane : public QWidget {
Q_OBJECT
public:
CollapsibleInfoPane(QWidget* parent = 0);
struct Data {
Data() : type_(Type_Biography), relevance_(0) {}
void SetTitle(const QString& title);
void SetWidget(QWidget* widget);
bool operator <(const Data& other) const;
enum Type {
Type_Biography = 0,
TypeCount
};
QString title_;
QIcon icon_;
Type type_;
int relevance_;
QWidget* contents_;
};
CollapsibleInfoPane(const Data& data, QWidget* parent = 0);
const Data& data() const { return data_; }
public slots:
void Expand();
@ -38,8 +57,8 @@ private slots:
void ExpandedToggled(bool expanded);
private:
Data data_;
CollapsibleInfoHeader* header_;
QWidget* widget_;
};
#endif // COLLAPSIBLEINFOPANE_H

View File

@ -32,6 +32,20 @@ struct EchoNestArtistInfo::Request {
EchoNestArtistInfo::EchoNestArtistInfo(QObject* parent)
: ArtistInfoProvider(parent)
{
site_relevance_["wikipedia"] = 100;
site_relevance_["last.fm"] = 60;
site_relevance_["lastfm"] = 60;
site_relevance_["amazon"] = 30;
site_icons_["amazon"] = QIcon(":/providers/amazon.png");
site_icons_["aol"] = QIcon(":/providers/aol.png");
site_icons_["cdbaby"] = QIcon(":/providers/cdbaby.png");
site_icons_["lastfm"] = QIcon(":/last.fm/as.png");
site_icons_["last.fm"] = QIcon(":/last.fm/as.png");
site_icons_["mog"] = QIcon(":/providers/mog.png");
site_icons_["mtvmusic"] = QIcon(":/providers/mtvmusic.png");
site_icons_["myspace"] = QIcon(":/providers/myspace.png");
site_icons_["wikipedia"] = QIcon(":/providers/wikipedia.png");
}
void EchoNestArtistInfo::FetchInfo(int id, const QString& artist_name) {
@ -84,10 +98,27 @@ void EchoNestArtistInfo::ImagesFinished() {
void EchoNestArtistInfo::BiographiesFinished() {
RequestPtr request = ReplyFinished(qobject_cast<QNetworkReply*>(sender()));
foreach (const Echonest::Biography& bio, request->artist_->biographies()) {
QTextEdit* editor = new AutoSizedTextEdit;
editor->setHtml(bio.text());
QSet<QString> already_seen;
emit InfoReady(request->id_, tr("Biography from %1").arg(bio.site()), editor);
foreach (const Echonest::Biography& bio, request->artist_->biographies()) {
if (already_seen.contains(bio.text()))
continue;
already_seen.insert(bio.text());
CollapsibleInfoPane::Data data;
data.title_ = tr("Biography from %1").arg(bio.site());
data.type_ = CollapsibleInfoPane::Data::Type_Biography;
const QString site = bio.site().toLower();
if (site_relevance_.contains(site))
data.relevance_ = site_relevance_[site];
if (site_icons_.contains(site))
data.icon_ = site_icons_[site];
AutoSizedTextEdit* editor = new AutoSizedTextEdit;
editor->setHtml(bio.text());
data.contents_ = editor;
emit InfoReady(request->id_, data);
}
}

View File

@ -19,6 +19,8 @@
#include "artistinfoprovider.h"
#include <QMap>
#include <boost/shared_ptr.hpp>
class QNetworkReply;
@ -44,6 +46,8 @@ private:
private:
QList<RequestPtr> requests_;
QMap<QString, int> site_relevance_;
QMap<QString, QIcon> site_icons_;
};
#endif // ECHONESTARTISTINFO_H

View File

@ -25,7 +25,7 @@ SongInfoBase::SongInfoBase(NetworkAccessManager* network, QWidget* parent)
void SongInfoBase::SongChanged(const Song& metadata) {
if (isVisible()) {
Update(metadata);
MaybeUpdate(metadata);
dirty_ = false;
} else {
queued_metadata_ = metadata;
@ -39,8 +39,19 @@ void SongInfoBase::SongFinished() {
void SongInfoBase::showEvent(QShowEvent* e) {
if (dirty_) {
Update(queued_metadata_);
MaybeUpdate(queued_metadata_);
dirty_ = false;
}
QWidget::showEvent(e);
}
void SongInfoBase::MaybeUpdate(const Song& metadata) {
if (old_metadata_.is_valid()) {
if (!NeedsUpdate(old_metadata_, metadata)) {
return;
}
}
Update(metadata);
old_metadata_ = metadata;
}

View File

@ -37,11 +37,16 @@ protected:
void showEvent(QShowEvent* e);
virtual void Update(const Song& metadata) {}
virtual bool NeedsUpdate(const Song& old_metadata, const Song& new_metadata) const { return true; }
NetworkAccessManager* network_;
private:
void MaybeUpdate(const Song& metadata);
private:
Song queued_metadata_;
Song old_metadata_;
bool dirty_;
};

View File

@ -99,7 +99,7 @@
</property>
<property name="icon">
<iconset resource="../../data/data.qrc">
<normaloff>:/magnatune.png</normaloff>:/magnatune.png</iconset>
<normaloff>:/providers/magnatune.png</normaloff>:/providers/magnatune.png</iconset>
</property>
</item>
</widget>

View File

@ -16,6 +16,8 @@
#include "autosizedtextedit.h"
#include <QWheelEvent>
AutoSizedTextEdit::AutoSizedTextEdit(QWidget* parent)
: QTextEdit(parent)
{
@ -28,8 +30,14 @@ void AutoSizedTextEdit::resizeEvent(QResizeEvent* e) {
document()->setTextWidth(w);
setMinimumHeight(document()->size().height());
QTextEdit::resizeEvent(e);
}
QSize AutoSizedTextEdit::sizeHint() const {
return minimumSize();
}
void AutoSizedTextEdit::wheelEvent(QWheelEvent* e) {
e->ignore();
}

View File

@ -29,6 +29,7 @@ public:
protected:
void resizeEvent(QResizeEvent* e);
void wheelEvent(QWheelEvent* e);
};
#endif // AUTOSIZEDTEXTEDIT_H

View File

@ -152,7 +152,7 @@ void OSD::MagnatuneDownloadFinished(const QStringList& albums) {
message = tr("%1 albums").arg(albums.count());
ShowMessage(tr("Magnatune download finished"), message, QString(),
QImage(":magnatune.png"));
QImage(":/providers/magnatune.png"));
}
void OSD::ShowMessage(const QString& summary,