1 /****************************************************************************************
2 * Copyright (c) 2010 Rick W. Chen <stuffcorpse@archlinux.us> *
3 * *
4 * This program is free software; you can redistribute it and/or modify it under *
5 * the terms of the GNU General Public License as published by the Free Software *
6 * Foundation; either version 2 of the License, or (at your option) any later *
7 * version. *
8 * *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
11 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
12 * *
13 * You should have received a copy of the GNU General Public License along with *
14 * this program. If not, see <http://www.gnu.org/licenses/>. *
15 ****************************************************************************************/
16
17 #include "UpcomingEventsMapWidget.h"
18 #include "UpcomingEventsWidget.h"
19 #include "network/NetworkAccessManagerProxy.h"
20
21 #include <KLocale>
22 #include <KStandardDirs>
23
24 #include <QDesktopServices>
25 #include <QFile>
26 #include <QFileInfo>
27 #include <QTimer>
28 #include <QWebView>
29 #include <QWebFrame>
30
31 class UpcomingEventsMapWidgetPrivate
32 {
33 public:
34 UpcomingEventsMapWidgetPrivate( UpcomingEventsMapWidget *parent );
35 ~UpcomingEventsMapWidgetPrivate();
36
37 void addEvent( const LastFmEventPtr &event );
38 void addMarker( const LastFmEventPtr &event );
39 QString createInfoString( const LastFmEventPtr &event ) const;
40 QUrl eventForMapIcon( const LastFmEventPtr &event ) const;
41 void removeEvent( const LastFmEventPtr &event );
42 void removeMarker( const LastFmEventPtr &event );
43
44 void _init();
45 void _linkClicked( const QUrl &url );
46 void _loadFinished( bool success );
47 void _centerAt( QObject *obj );
48
49 LastFmEvent::List events;
50 LastFmEvent::List eventQueue;
51 QSet<UpcomingEventsListWidget*> listWidgets;
52 QPointF centerWhenLoaded;
53 bool isLoaded;
54
55 private:
56 UpcomingEventsMapWidget *const q_ptr;
57 Q_DECLARE_PUBLIC( UpcomingEventsMapWidget )
58 };
59
UpcomingEventsMapWidgetPrivate(UpcomingEventsMapWidget * parent)60 UpcomingEventsMapWidgetPrivate::UpcomingEventsMapWidgetPrivate( UpcomingEventsMapWidget *parent )
61 : isLoaded( false )
62 , q_ptr( parent )
63 {
64 }
65
~UpcomingEventsMapWidgetPrivate()66 UpcomingEventsMapWidgetPrivate::~UpcomingEventsMapWidgetPrivate()
67 {
68 }
69
70 void
addEvent(const LastFmEventPtr & event)71 UpcomingEventsMapWidgetPrivate::addEvent( const LastFmEventPtr &event )
72 {
73 if( !isLoaded )
74 {
75 eventQueue << event;
76 return;
77 }
78 events << event;
79 addMarker( event );
80 }
81
82 void
addMarker(const LastFmEventPtr & event)83 UpcomingEventsMapWidgetPrivate::addMarker( const LastFmEventPtr &event )
84 {
85 Q_Q( UpcomingEventsMapWidget );
86 LastFmLocationPtr loc = event->venue()->location;
87 QString js = QString( "javascript:addMarker(%1,%2,'%3','%4')" )
88 .arg( QString::number( loc->latitude ) )
89 .arg( QString::number( loc->longitude ) )
90 .arg( eventForMapIcon(event).url() )
91 .arg( createInfoString(event) );
92 q->page()->mainFrame()->evaluateJavaScript( js );
93 }
94
95 void
removeEvent(const LastFmEventPtr & event)96 UpcomingEventsMapWidgetPrivate::removeEvent( const LastFmEventPtr &event )
97 {
98 eventQueue.removeAll( event );
99 if( isLoaded )
100 {
101 events.removeAll( event );
102 removeMarker( event );
103 }
104 }
105
106 void
removeMarker(const LastFmEventPtr & event)107 UpcomingEventsMapWidgetPrivate::removeMarker( const LastFmEventPtr &event )
108 {
109 Q_Q( UpcomingEventsMapWidget );
110 LastFmLocationPtr loc = event->venue()->location;
111 QString js = QString( "javascript:removeMarker(%1,%2)" )
112 .arg( QString::number( loc->latitude ) )
113 .arg( QString::number( loc->longitude ) );
114 q->page()->mainFrame()->evaluateJavaScript( js );
115 }
116
117 QString
createInfoString(const LastFmEventPtr & event) const118 UpcomingEventsMapWidgetPrivate::createInfoString( const LastFmEventPtr &event ) const
119 {
120 QString name = event->name();
121 if( event->isCancelled() )
122 name = i18nc( "@label:textbox Title for a canceled upcoming event", "<s>%1</s> (Canceled)", name );
123
124 QStringList artists = event->artists();
125 artists.removeDuplicates();
126
127 QString desc = event->description();
128 KDateTime dt = event->date();
129 QStringList tags = event->tags();
130 LastFmVenuePtr venue = event->venue();
131 QString venueWebsite = venue->website.url();
132 QString venueLastFmUrl = venue->url.url();
133 QString location = venue->location->city;
134 if( !venue->location->street.isEmpty() )
135 location.prepend( venue->location->street + ", " );
136
137 QString html = QString(
138 "<div><img src=\"%1\" alt=\"\" style=\"float:right;margin:5px;clear:right\"/></div>" \
139 "<div><img src=\"%2\" alt=\"\" style=\"float:right;margin:5px;clear:right\"/></div>" \
140 "<div id=\"bodyContent\">" \
141 "<small>" \
142 "<b>Event:</b> %3<br/>" \
143 "<b>Artists:</b> %4<br/>" \
144 "<b>Time:</b> %5<br/>" \
145 "<b>Date:</b> %6<br/>" \
146 "<b>Venue:</b> %7<br/>" \
147 "<b>Location:</b> %8<br/>" \
148 "<b>Description:</b> %9<br/>" \
149 "<b>Tags:</b> %10<br/>" \
150 "<b>Event Website:</b> <a href=\"%11\">Last.fm</a><br/>" \
151 "<b>Venue Website:</b> <a href=\"%12\">URL</a>, <a href=\"%13\">Last.fm</a><br/>" \
152 "</small>" \
153 "</div>")
154 .arg( event->imageUrl(LastFmEvent::Medium).url() )
155 .arg( venue->imageUrls[LastFmEvent::Medium].url() )
156 .arg( name )
157 .arg( artists.join(", ") )
158 .arg( KGlobal::locale()->formatTime( dt.time() ) )
159 .arg( KGlobal::locale()->formatDate( dt.date(), KLocale::FancyShortDate ) )
160 .arg( venue->name )
161 .arg( location )
162 .arg( desc.isEmpty() ? i18n("none") : desc )
163 .arg( tags.isEmpty() ? i18n("none") : tags.join(", ") )
164 .arg( event->url().url() )
165 .arg( venueWebsite.isEmpty() ? i18n("none") : venueWebsite )
166 .arg( venueLastFmUrl.isEmpty() ? i18n("none") : venueLastFmUrl );
167 return html;
168 }
169
170 QUrl
eventForMapIcon(const LastFmEventPtr & event) const171 UpcomingEventsMapWidgetPrivate::eventForMapIcon( const LastFmEventPtr &event ) const
172 {
173 // Thanks a whole bunch to Nicolas Mollet, Matthias Stasiak at google-maps-icons
174 // pack (http://code.google.com/p/google-maps-icons/wiki/CultureIcons)
175 const QStringList &tags = event->tags();
176 QString name;
177 if( tags.contains( "festival", Qt::CaseInsensitive ) )
178 name = "festival.png";
179 else if( !tags.filter( QRegExp("rock|metal") ).isEmpty() )
180 name = "music-rock.png";
181 else if( !tags.filter( QRegExp("hip.?hop|rap") ).isEmpty() )
182 name = "music-hiphop.png";
183 else if( !tags.filter( QRegExp("orchest.*|classical|symphon.*") ).isEmpty() )
184 name = "music-classical.png";
185 else if( !tags.filter( QRegExp("choir|chorus|choral") ).isEmpty() )
186 name = "choral.png";
187 else if( !tags.filter( QRegExp("danc(e|ing)|disco|electronic") ).isEmpty() )
188 name = "dancinghall.png";
189 else
190 name = "music-live.png";
191 return QUrl( "http://google-maps-icons.googlecode.com/files/" + name );
192 }
193
194 void
_centerAt(QObject * obj)195 UpcomingEventsMapWidgetPrivate::_centerAt( QObject *obj )
196 {
197 Q_Q( UpcomingEventsMapWidget );
198 UpcomingEventsWidget *widget = static_cast<UpcomingEventsWidget*>( obj );
199 LastFmVenuePtr venue = widget->eventPtr()->venue();
200 q->centerAt( venue );
201 }
202
203 void
_init()204 UpcomingEventsMapWidgetPrivate::_init()
205 {
206 Q_Q( UpcomingEventsMapWidget );
207 q->connect( q, SIGNAL(loadFinished(bool)), q, SLOT(_loadFinished(bool)) );
208 QFile mapHtml( KStandardDirs::locate( "data", "amarok/data/upcoming-events-map.html" ) );
209 if( mapHtml.open( QIODevice::ReadOnly | QIODevice::Text ) )
210 q->setHtml( mapHtml.readAll() );
211 }
212
213 void
_linkClicked(const QUrl & url)214 UpcomingEventsMapWidgetPrivate::_linkClicked( const QUrl &url )
215 {
216 QDesktopServices::openUrl( url );
217 }
218
219 void
_loadFinished(bool success)220 UpcomingEventsMapWidgetPrivate::_loadFinished( bool success )
221 {
222 if( !success )
223 return;
224
225 Q_Q( UpcomingEventsMapWidget );
226 isLoaded = true;
227 LastFmEvent::List queue = eventQueue;
228 eventQueue.clear();
229
230 foreach( const LastFmEventPtr &event, queue )
231 addEvent( event );
232
233 if( !centerWhenLoaded.isNull() )
234 {
235 q->centerAt( centerWhenLoaded.y(), centerWhenLoaded.x() );
236 centerWhenLoaded *= 0.0;
237 }
238 }
239
UpcomingEventsMapWidget(QGraphicsItem * parent)240 UpcomingEventsMapWidget::UpcomingEventsMapWidget( QGraphicsItem *parent )
241 : KGraphicsWebView( parent )
242 , d_ptr( new UpcomingEventsMapWidgetPrivate( this ) )
243 {
244 page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
245 page()->setNetworkAccessManager( The::networkAccessManager() );
246 connect( page(), SIGNAL(linkClicked(QUrl)), this, SLOT(_linkClicked(QUrl)) );
247 QTimer::singleShot( 0, this, SLOT(_init()) );
248 }
249
~UpcomingEventsMapWidget()250 UpcomingEventsMapWidget::~UpcomingEventsMapWidget()
251 {
252 delete d_ptr;
253 }
254
255 void
addEvent(const LastFmEventPtr & event)256 UpcomingEventsMapWidget::addEvent( const LastFmEventPtr &event )
257 {
258 Q_D( UpcomingEventsMapWidget );
259 d->addEvent( event );
260 }
261
262 void
addEvents(const LastFmEvent::List & events)263 UpcomingEventsMapWidget::addEvents( const LastFmEvent::List &events )
264 {
265 foreach( const LastFmEventPtr &event, events )
266 addEvent( event );
267 }
268
269 void
addEventsListWidget(UpcomingEventsListWidget * widget)270 UpcomingEventsMapWidget::addEventsListWidget( UpcomingEventsListWidget *widget )
271 {
272 Q_D( UpcomingEventsMapWidget );
273 if( widget )
274 {
275 d->listWidgets << widget;
276 addEvents( widget->events() );
277 connect( widget, SIGNAL(eventAdded(LastFmEventPtr)), this, SLOT(addEvent(LastFmEventPtr)) );
278 connect( widget, SIGNAL(eventRemoved(LastFmEventPtr)), this, SLOT(removeEvent(LastFmEventPtr)) );
279 connect( widget, SIGNAL(mapRequested(QObject*)), this, SLOT(_centerAt(QObject*)) );
280 }
281 }
282
283 void
removeEventsListWidget(UpcomingEventsListWidget * widget)284 UpcomingEventsMapWidget::removeEventsListWidget( UpcomingEventsListWidget *widget )
285 {
286 Q_D( UpcomingEventsMapWidget );
287 if( d->listWidgets.contains( widget ) )
288 {
289 foreach( const LastFmEventPtr &event, widget->events() )
290 removeEvent( event );
291 d->listWidgets.remove( widget );
292 widget->disconnect( this );
293 }
294 }
295
296 void
removeEvent(const LastFmEventPtr & event)297 UpcomingEventsMapWidget::removeEvent( const LastFmEventPtr &event )
298 {
299 Q_D( UpcomingEventsMapWidget );
300 d->removeEvent( event );
301 }
302
303 bool
isLoaded() const304 UpcomingEventsMapWidget::isLoaded() const
305 {
306 Q_D( const UpcomingEventsMapWidget );
307 return d->isLoaded;
308 }
309
310 int
eventCount() const311 UpcomingEventsMapWidget::eventCount() const
312 {
313 Q_D( const UpcomingEventsMapWidget );
314 return d->events.count();
315 }
316
317 LastFmEvent::List
events() const318 UpcomingEventsMapWidget::events() const
319 {
320 Q_D( const UpcomingEventsMapWidget );
321 return d->events;
322 }
323
324 void
centerAt(double latitude,double longitude)325 UpcomingEventsMapWidget::centerAt( double latitude, double longitude )
326 {
327 Q_D( UpcomingEventsMapWidget );
328 if( !d->isLoaded )
329 {
330 QPointF geo( longitude, latitude );
331 d->centerWhenLoaded = geo;
332 return;
333 }
334
335 QString lat( QString::number( latitude ) );
336 QString lng( QString::number( longitude ) );
337 QString js = QString( "javascript:centerAt(%1,%2)" ).arg( lat ).arg( lng );
338 page()->mainFrame()->evaluateJavaScript( js );
339 }
340
341 void
centerAt(const LastFmVenuePtr & venue)342 UpcomingEventsMapWidget::centerAt( const LastFmVenuePtr &venue )
343 {
344 LastFmLocationPtr loc = venue->location;
345 centerAt( loc->latitude, loc->longitude );
346 }
347
348 void
clear()349 UpcomingEventsMapWidget::clear()
350 {
351 Q_D( UpcomingEventsMapWidget );
352 d->events.clear();
353 page()->mainFrame()->evaluateJavaScript( "javascript:clearMarkers()" );
354 }
355
356