From 5940b0ead16dba13da388cc6deb1d5b1f5cb6a7d Mon Sep 17 00:00:00 2001 From: David Sansome Date: Mon, 27 Aug 2012 12:24:28 +0100 Subject: [PATCH] Make SongKick results a bit prettier --- src/CMakeLists.txt | 3 + src/core/utilities.cpp | 18 +++ src/core/utilities.h | 1 + src/globalsearch/globalsearchview.ui | 6 +- src/songinfo/songinfotextview.cpp | 14 ++- src/songinfo/songinfotextview.h | 3 + src/songinfo/songkickconcerts.cpp | 108 +++++++----------- src/songinfo/songkickconcerts.h | 1 - src/songinfo/songkickconcertwidget.cpp | 147 +++++++++++++++++++++++++ src/songinfo/songkickconcertwidget.h | 60 ++++++++++ src/songinfo/songkickconcertwidget.ui | 99 +++++++++++++++++ 11 files changed, 381 insertions(+), 79 deletions(-) create mode 100644 src/songinfo/songkickconcertwidget.cpp create mode 100644 src/songinfo/songkickconcertwidget.h create mode 100644 src/songinfo/songkickconcertwidget.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70fc9eb22..2c09d217d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -282,6 +282,7 @@ set(SOURCES songinfo/songinfotextview.cpp songinfo/songinfoview.cpp songinfo/songkickconcerts.cpp + songinfo/songkickconcertwidget.cpp songinfo/songplaystats.cpp songinfo/ultimatelyricsprovider.cpp songinfo/ultimatelyricsreader.cpp @@ -539,6 +540,7 @@ set(HEADERS songinfo/songinfotextview.h songinfo/songinfoview.h songinfo/songkickconcerts.h + songinfo/songkickconcertwidget.h songinfo/songplaystats.h songinfo/ultimatelyricsprovider.h songinfo/ultimatelyricsreader.h @@ -652,6 +654,7 @@ set(UI smartplaylists/searchtermwidget.ui smartplaylists/wizardfinishpage.ui + songinfo/songkickconcertwidget.ui songinfo/songinfosettingspage.ui transcoder/transcodedialog.ui diff --git a/src/core/utilities.cpp b/src/core/utilities.cpp index f08c96c82..fd8a11e47 100644 --- a/src/core/utilities.cpp +++ b/src/core/utilities.cpp @@ -126,6 +126,24 @@ QString Ago(int seconds_since_epoch, const QLocale& locale) { return then.date().toString(locale.dateFormat()); } +QString PrettyFutureDate(const QDate& date) { + const QDate now = QDate::currentDate(); + const int delta_days = now.daysTo(date); + + if (delta_days < 0) + return QString(); + if (delta_days == 0) + return tr("Today"); + if (delta_days == 1) + return tr("Tomorrow"); + if (delta_days <= 7) + return tr("In %1 days").arg(delta_days); + if (delta_days <= 14) + return tr("Next week"); + + return tr("In %1 weeks").arg(delta_days / 7); +} + QString PrettySize(quint64 bytes) { QString ret; diff --git a/src/core/utilities.h b/src/core/utilities.h index 6bc2c3b08..93bdd8962 100644 --- a/src/core/utilities.h +++ b/src/core/utilities.h @@ -41,6 +41,7 @@ namespace Utilities { QString WordyTime(quint64 seconds); QString WordyTimeNanosec(qint64 nanoseconds); QString Ago(int seconds_since_epoch, const QLocale& locale); + QString PrettyFutureDate(const QDate& date); QString ColorToRgba(const QColor& color); diff --git a/src/globalsearch/globalsearchview.ui b/src/globalsearch/globalsearchview.ui index fdf6ea711..c958f05f5 100644 --- a/src/globalsearch/globalsearchview.ui +++ b/src/globalsearch/globalsearchview.ui @@ -23,7 +23,7 @@ 0 - + Search for anything @@ -44,7 +44,7 @@ - 0 + 1 @@ -97,7 +97,7 @@ 0 0 435 - 603 + 605 diff --git a/src/songinfo/songinfotextview.cpp b/src/songinfo/songinfotextview.cpp index 385dcc79b..bd66ef7ea 100644 --- a/src/songinfo/songinfotextview.cpp +++ b/src/songinfo/songinfotextview.cpp @@ -40,14 +40,20 @@ SongInfoTextView::SongInfoTextView(QWidget* parent) ReloadSettings(); } -void SongInfoTextView::ReloadSettings() { +qreal SongInfoTextView::FontSize() { QSettings s; s.beginGroup(kSettingsGroup); + return s.value("font_size", kDefaultFontSize).toReal(); +} - qreal size = s.value("font_size", kDefaultFontSize).toReal(); +QFont SongInfoTextView::Font() { QFont font; - font.setPointSizeF(size); - document()->setDefaultFont(font); + font.setPointSizeF(FontSize()); + return font; +} + +void SongInfoTextView::ReloadSettings() { + document()->setDefaultFont(Font()); } void SongInfoTextView::resizeEvent(QResizeEvent* e) { diff --git a/src/songinfo/songinfotextview.h b/src/songinfo/songinfotextview.h index 3b9fe7b7e..e305bb99a 100644 --- a/src/songinfo/songinfotextview.h +++ b/src/songinfo/songinfotextview.h @@ -29,6 +29,9 @@ public: static const qreal kDefaultFontSize; static const char* kSettingsGroup; + static qreal FontSize(); + static QFont Font(); + QSize sizeHint() const; public slots: diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp index 46d36c32c..bf0bf3023 100644 --- a/src/songinfo/songkickconcerts.cpp +++ b/src/songinfo/songkickconcerts.cpp @@ -18,6 +18,7 @@ #include "songkickconcerts.h" #include +#include #include #include @@ -25,7 +26,7 @@ #include #include "core/closure.h" -#include "songinfotextview.h" +#include "songkickconcertwidget.h" const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick"; const char* SongkickConcerts::kSongkickArtistCalendarUrl = @@ -98,15 +99,6 @@ void SongkickConcerts::FetchSongkickCalendar(const QString& artist_id, int id) { } void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { - static const char* kStaticMapUrl = - "http://maps.googleapis.com/maps/api/staticmap" - "?key=AIzaSyDDJqmLOeE1mY_EBONhnQmdXbKtasgCtqg" - "&sensor=false" - "&size=100x100" - "&zoom=12" - "¢er=%1,%2" - "&markers=%1,%2"; - QJson::Parser parser; bool ok = false; QVariant result = parser.parse(reply, &ok); @@ -117,10 +109,6 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { return; } - QString html; - QXmlStreamWriter writer(&html); - SongInfoTextView* text_view = new SongInfoTextView; - QVariantMap root = result.toMap(); QVariantMap results_page = root["resultsPage"].toMap(); QVariantMap results = results_page["results"].toMap(); @@ -131,55 +119,46 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { return; } + QWidget* container = new QWidget; + QVBoxLayout* layout = new QVBoxLayout(container); + foreach (const QVariant& v, events) { QVariantMap event = v.toMap(); - { - QString display_name = event["displayName"].toString(); - QVariantMap venue = event["venue"].toMap(); - const bool valid_latlng = - venue["lng"].isValid() && venue["lat"].isValid(); + QString display_name = event["displayName"].toString(); + QString start_date = event["start"].toMap()["date"].toString(); + QString city = event["location"].toMap()["city"].toString(); + QString uri = event["uri"].toString(); - if (valid_latlng && latlng_.IsValid()) { - static const int kFilterDistanceMetres = 250 * 1e3; // 250km - Geolocator::LatLng latlng( - venue["lat"].toString(), venue["lng"].toString()); - if (latlng_.IsValid() && latlng.IsValid()) { - int distance_metres = latlng_.Distance(latlng); - if (distance_metres > kFilterDistanceMetres) { - qLog(Debug) << "Filtered concert:" - << display_name - << "as too far away:" - << distance_metres; - continue; - } + // Try to get the lat/lng coordinates of the venue. + QVariantMap venue = event["venue"].toMap(); + const bool valid_latlng = + venue["lng"].isValid() && venue["lat"].isValid(); + + if (valid_latlng && latlng_.IsValid()) { + static const int kFilterDistanceMetres = 250 * 1e3; // 250km + Geolocator::LatLng latlng( + venue["lat"].toString(), venue["lng"].toString()); + if (latlng_.IsValid() && latlng.IsValid()) { + int distance_metres = latlng_.Distance(latlng); + if (distance_metres > kFilterDistanceMetres) { + qLog(Debug) << "Filtered concert:" + << display_name + << "as too far away:" + << distance_metres; + continue; } } - - writer.writeStartElement("div"); - { - writer.writeStartElement("a"); - writer.writeAttribute("href", event["uri"].toString()); - writer.writeCharacters(display_name); - writer.writeEndElement(); - } - if (valid_latlng) { - writer.writeStartElement("img"); - QString maps_url = QString(kStaticMapUrl).arg( - venue["lat"].toString(), - venue["lng"].toString()); - writer.writeAttribute("src", maps_url); - writer.writeEndElement(); - - // QTextDocument does not support loading remote images, so we load - // them here and then inject them into the document later. - QNetworkRequest request(maps_url); - QNetworkReply* reply = network_.get(request); - NewClosure(reply, SIGNAL(finished()), this, - SLOT(InjectImage(QNetworkReply*, SongInfoTextView*)), - reply, text_view); - } - writer.writeEndElement(); } + + SongKickConcertWidget* widget = new SongKickConcertWidget(container); + widget->Init(display_name, uri, start_date, city); + + if (valid_latlng) { + widget->SetMap(venue["lat"].toString(), venue["lng"].toString(), + venue["displayName"].toString()); + } + + layout->addWidget(widget); } CollapsibleInfoPane::Data data; @@ -187,25 +166,12 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { data.id_ = QString("songkick/%1").arg(id); data.title_ = tr("Upcoming Concerts"); data.icon_ = QIcon(":providers/songkick.png"); - - text_view->SetHtml(html); - data.contents_ = text_view; + data.contents_ = container; emit InfoReady(id, data); emit Finished(id); } -void SongkickConcerts::InjectImage( - QNetworkReply* reply, SongInfoTextView* text_view) { - reply->deleteLater(); - QImage image; - image.load(reply, "png"); - text_view->document()->addResource( - QTextDocument::ImageResource, - reply->request().url(), - QVariant(image)); -} - void SongkickConcerts::GeolocateFinished(Geolocator::LatLng latlng) { latlng_ = latlng; } diff --git a/src/songinfo/songkickconcerts.h b/src/songinfo/songkickconcerts.h index e9cdee120..e205011f1 100644 --- a/src/songinfo/songkickconcerts.h +++ b/src/songinfo/songkickconcerts.h @@ -37,7 +37,6 @@ class SongkickConcerts : public SongInfoProvider { private slots: void ArtistSearchFinished(QNetworkReply* reply, int id); void CalendarRequestFinished(QNetworkReply* reply, int id); - void InjectImage(QNetworkReply* reply, SongInfoTextView* text_view); void GeolocateFinished(Geolocator::LatLng latlng); private: diff --git a/src/songinfo/songkickconcertwidget.cpp b/src/songinfo/songkickconcertwidget.cpp new file mode 100644 index 000000000..2ba6c4e8a --- /dev/null +++ b/src/songinfo/songkickconcertwidget.cpp @@ -0,0 +1,147 @@ +/* This file is part of Clementine. + Copyright 2012, 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 "songinfotextview.h" +#include "songkickconcertwidget.h" +#include "ui_songkickconcertwidget.h" +#include "core/closure.h" +#include "core/network.h" +#include "core/utilities.h" + +#include +#include +#include +#include + +const int SongKickConcertWidget::kStaticMapWidth = 100; +const int SongKickConcertWidget::kStaticMapHeight = 100; + + +SongKickConcertWidget::SongKickConcertWidget(QWidget* parent) + : QWidget(parent), + ui_(new Ui_SongKickConcertWidget), + network_(new NetworkAccessManager(this)) +{ + ui_->setupUi(this); + + // Hide the map by default + ui_->map->hide(); + ui_->map->setFixedSize(kStaticMapWidth, kStaticMapHeight); + ui_->map->installEventFilter(this); + + ReloadSettings(); +} + +SongKickConcertWidget::~SongKickConcertWidget() { + delete ui_; +} + +void SongKickConcertWidget::ReloadSettings() { + QFont font(SongInfoTextView::Font()); + ui_->title->setFont(font); + ui_->date->setFont(font); + ui_->location->setFont(font); +} + +void SongKickConcertWidget::Init(const QString& title, const QString& url, + const QString& date, const QString& location) { + ui_->title->setText(QString("%2").arg( + Qt::escape(url), + Qt::escape(title))); + + if (!location.isEmpty()) { + ui_->location->setText(location); + } else { + ui_->location->hide(); + } + + if (!date.isEmpty()) { + QDate parsed_date(QDate::fromString(date, Qt::ISODate)); + QString date_text = Utilities::PrettyFutureDate(parsed_date); + + if (date_text.isEmpty()) { + date_text = date; + } else { + date_text += " (" + date + ")"; + } + + ui_->date->setText(date_text); + } else { + ui_->date->hide(); + } +} + +void SongKickConcertWidget::SetMap(const QString& lat, const QString& lng, + const QString& venue_name) { + static const char* kStaticMapUrl = + "http://maps.googleapis.com/maps/api/staticmap" + "?key=AIzaSyDDJqmLOeE1mY_EBONhnQmdXbKtasgCtqg" + "&sensor=false" + "&size=%1x%2" + "&zoom=12" + "¢er=%3,%4" + "&markers=%3,%4"; + + ui_->map->show(); + + map_url_ = QUrl("https://maps.google.com/"); + map_url_.addQueryItem("ll", QString("%1,%2").arg(lat, lng)); + if (!venue_name.isEmpty()) { + map_url_.addQueryItem("q", venue_name); + } + + // Request the static map image + const QUrl url(QString(kStaticMapUrl).arg( + QString::number(kStaticMapWidth), + QString::number(kStaticMapHeight), + lat, lng)); + QNetworkReply* reply = network_->get(QNetworkRequest(url)); + NewClosure(reply, SIGNAL(finished()), + this, SLOT(MapLoaded(QNetworkReply*)), + reply); +} + +void SongKickConcertWidget::MapLoaded(QNetworkReply* reply) { + reply->deleteLater(); + + QImage image; + if (!image.load(reply, "PNG")) { + qLog(Warning) << "Failed to load static map image" << reply->url(); + return; + } + + // Scale it if it was the wrong size. + if (image.width() != kStaticMapWidth || image.height() != kStaticMapHeight) { + qLog(Warning) << "Scaling static map image" << image.size(); + image = image.scaled(kStaticMapWidth, kStaticMapHeight, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + + ui_->map->setPixmap(QPixmap::fromImage(image)); +} + +bool SongKickConcertWidget::eventFilter(QObject* object, QEvent* event) { + if (object == ui_->map && event->type() == QEvent::MouseButtonRelease) { + QMouseEvent* e = dynamic_cast(event); + if (e->button() == Qt::LeftButton) { + QDesktopServices::openUrl(map_url_); + return true; + } + } + + return QWidget::eventFilter(object, event); +} diff --git a/src/songinfo/songkickconcertwidget.h b/src/songinfo/songkickconcertwidget.h new file mode 100644 index 000000000..faf05b979 --- /dev/null +++ b/src/songinfo/songkickconcertwidget.h @@ -0,0 +1,60 @@ +/* This file is part of Clementine. + Copyright 2012, 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 SONGKICKCONCERTWIDGET_H +#define SONGKICKCONCERTWIDGET_H + +#include +#include + +class Ui_SongKickConcertWidget; + +class QNetworkAccessManager; +class QNetworkReply; + +class SongKickConcertWidget : public QWidget { + Q_OBJECT + +public: + SongKickConcertWidget(QWidget* parent = 0); + ~SongKickConcertWidget(); + + static const int kStaticMapWidth; + static const int kStaticMapHeight; + + void Init(const QString& title, const QString& url, + const QString& date, const QString& location); + void SetMap(const QString& lat, const QString& lng, + const QString& venue_name); + + // QObject + bool eventFilter(QObject* object, QEvent* event); + +public slots: + void ReloadSettings(); + +private slots: + void MapLoaded(QNetworkReply* reply); + +private: + Ui_SongKickConcertWidget* ui_; + QNetworkAccessManager* network_; + + QUrl map_url_; +}; + +#endif // SONGKICKCONCERTWIDGET_H diff --git a/src/songinfo/songkickconcertwidget.ui b/src/songinfo/songkickconcertwidget.ui new file mode 100644 index 000000000..056bc4979 --- /dev/null +++ b/src/songinfo/songkickconcertwidget.ui @@ -0,0 +1,99 @@ + + + SongKickConcertWidget + + + + 0 + 0 + 571 + 195 + + + + Form + + + #location, #date { +color: #666; +} + + + + + 0 + + + + + 0 + + + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + + + + + +