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