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
+
+
+
+
+
+
+
+