3 """Simple program to display local gigs
5 Intended for use on the N900, uses the devices gps to find local gigs.
8 __authors__ = ["Jon Staley"]
9 __copyright__ = "Copyright 2010 Jon Staley"
13 from xml.dom.minidom import parseString
14 from datetime import datetime, date
22 from threading import Thread
25 from locator import LocationUpdater
27 gtk.gdk.threads_init()
31 def parse_xml(self, xml, lat, long):
32 """ Parse xml into a dict """
35 dom = parseString(xml)
37 events = dom.getElementsByTagName('event')
39 start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
40 if start_date.date() == today:
41 title = event.getElementsByTagName('title')[0].childNodes[0].data
43 artists_element = event.getElementsByTagName('artists')[0]
45 for artist in artists_element.getElementsByTagName('artist'):
46 artist_list.append(artist.childNodes[0].data)
47 artists = ', '.join(artist_list)
49 venue_details = event.getElementsByTagName('venue')[0]
50 venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
51 address = self.get_address(venue_details.getElementsByTagName('location')[0])
52 geo_data = venue_details.getElementsByTagName('geo:point')[0]
53 venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
54 venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
55 distance = location.distance_between(float(lat),
60 events_list.append({'title': title,
68 def get_address(self, location):
69 """ Return the venues address details from the xml element """
74 if location.getElementsByTagName('street')[0].childNodes:
75 street = location.getElementsByTagName('street')[0].childNodes[0].data
76 if location.getElementsByTagName('city')[0].childNodes:
77 city = location.getElementsByTagName('city')[0].childNodes[0].data
78 if location.getElementsByTagName('country')[0].childNodes:
79 country = location.getElementsByTagName('country')[0].childNodes[0].data
80 if location.getElementsByTagName('postalcode')[0].childNodes:
81 postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
82 return '\n'.join([street, city, country, postalcode])
84 def parse_date(self, date_string):
85 """ Parse date string into datetime object """
86 fmt = "%a, %d %b %Y %H:%M:%S"
87 result = time.strptime(date_string, fmt)
88 return datetime(result.tm_year,
99 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
100 self.url_base = "http://ws.audioscrobbler.com/2.0/"
102 def get_events(self, lat, long, distance):
103 """ Retrieve xml and parse into events list """
104 xml = self.get_xml(lat, long, distance)
105 events = self.parser.parse_xml(xml,
108 return self.sort_events(events)
110 def sort_events(self, events):
111 """ Sort gig by distance """
112 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
115 def get_xml(self, lat, long, distance):
116 """ Return xml from lastfm """
117 method = "geo.getevents"
118 params = urllib.urlencode({'method': method,
119 'api_key': self.api_key,
120 'distance': distance,
123 response = urllib.urlopen(self.url_base, params)
124 return response.read()
126 def distance_cmp(self, x, y):
127 """ Compare distances for list sort """
143 self.parser = GigParser()
144 self.location = LocationUpdater()
145 self.events = Events()
146 self.win = hildon.StackableWindow()
147 self.app_title = "Gig Finder"
149 # Add user settings for distance, date
151 # maybe do km to mile conversions
154 """ Build the gui and start the update thread """
155 program = hildon.Program.get_instance()
156 menu = self.create_menu()
158 self.win.set_title(self.app_title)
159 self.win.connect("destroy", gtk.main_quit, None)
160 self.win.set_app_menu(menu)
162 Thread(target=self.update_gigs).start()
167 def show_about(self, widget, data):
168 """ Show about dialog """
169 dialog = gtk.AboutDialog()
170 dialog.set_name('Gig Finder')
171 dialog.set_version(__version__)
172 dialog.set_authors(__authors__)
173 dialog.set_comments('Display gigs close by.\nUsing the http://www.last.fm api.')
174 dialog.set_license('Distributed under the MIT license.\nhttp://www.opensource.org/licenses/mit-license.php')
175 dialog.set_copyright(__copyright__)
178 def update(self, widget, data):
179 """ Start update process """
180 self.win.set_title(self.app_title)
181 self.location.reset()
182 self.win.remove(self.pannable_area)
183 Thread(target=self.update_gigs).start()
185 def update_gigs(self):
187 gobject.idle_add(self.show_message, "Getting events")
188 gobject.idle_add(self.location.update_location)
191 # TODO: needs a timeout
192 while not self.location.lat or not self.location.long:
195 events = self.events.get_events(self.location.lat,
198 gobject.idle_add(self.hide_message)
199 gobject.idle_add(self.show_events, events)
202 def show_message(self, message):
203 """ Set window progress indicator and show message """
204 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
205 self.banner = hildon.hildon_banner_show_information(self.win,
209 def hide_message(self):
210 """ Hide banner and sete progress indicator """
212 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
214 def show_events(self, events):
215 """ Sort events, set new window title and add events to table """
217 self.win.set_title('%s (%s)' % (self.app_title, len(events)))
218 self.add_events(events)
220 label = gtk.Label('No events available')
221 vbox = gtk.VBox(False, 0)
222 vbox.pack_start(label, True, True, 0)
226 def create_menu(self):
227 """ Build application menu """
228 update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
229 update_button.set_label('Update')
230 update_button.connect('clicked',
234 about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
235 about_button.set_label('About')
236 about_button.connect('clicked',
240 menu = hildon.AppMenu()
241 menu.append(update_button)
242 menu.append(about_button)
246 def show_details(self, widget, data):
247 """ Open new window showing gig details """
248 win = hildon.StackableWindow()
249 win.set_title(data['title'])
251 win.vbox = gtk.VBox()
254 scroll = hildon.PannableArea()
255 win.vbox.pack_start(scroll, True, True, 0)
257 view = hildon.TextView()
258 view.set_editable(False)
259 view.unset_flags(gtk.CAN_FOCUS)
260 view.set_wrap_mode(gtk.WRAP_WORD)
261 buffer = view.get_buffer()
262 end = buffer.get_end_iter()
263 buffer.insert(end, '%s\n' % data['title'])
264 buffer.insert(end, 'Artists: %s\n' % data['artists'])
265 buffer.insert(end, 'Venue: %s\n' % data['venue'])
266 buffer.insert(end, '%s\n' % data['address'])
267 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M %d/%M/%Y'))
268 buffer.insert(end, '\n')
269 scroll.add_with_viewport(view)
274 """ Add table for events """
275 self.table = gtk.Table(columns=1)
276 self.table.set_row_spacings(10)
277 self.table.set_col_spacings(10)
279 self.pannable_area = hildon.PannableArea()
280 self.pannable_area.add_with_viewport(self.table)
281 self.pannable_area.show_all()
282 self.win.add(self.pannable_area)
284 def add_events(self, events):
285 """ Add a table of buttons """
289 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
290 hildon.BUTTON_ARRANGEMENT_VERTICAL)
291 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
292 button.connect("clicked", self.show_details, event)
293 self.table.attach(button, 0, 1, pos, pos+1)
295 self.table.show_all()
297 if __name__ == "__main__":