1 #!/usr/bin/env python2.5
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 gtk.gdk.threads_init()
29 def parse_xml(self, xml, lat, long):
30 """ Parse xml into a dict """
31 # TODO: filter to todays events only
34 dom = parseString(xml)
36 events = dom.getElementsByTagName('event')
38 title = event.getElementsByTagName('title')[0].childNodes[0].data
40 artists_element = event.getElementsByTagName('artists')[0]
42 for artist in artists_element.getElementsByTagName('artist'):
43 artist_list.append(artist.childNodes[0].data)
44 artists = ', '.join(artist_list)
46 venue_details = event.getElementsByTagName('venue')[0]
47 venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
48 address = self.get_address(venue_details.getElementsByTagName('location')[0])
49 geo_data = venue_details.getElementsByTagName('geo:point')[0]
50 venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
51 venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
52 distance = location.distance_between(float(lat),
57 start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
59 events_list.append({'title': title,
67 def get_address(self, location):
68 """ Return the venues address details from the xml element """
73 if location.getElementsByTagName('street')[0].childNodes:
74 street = location.getElementsByTagName('street')[0].childNodes[0].data
75 if location.getElementsByTagName('city')[0].childNodes:
76 city = location.getElementsByTagName('city')[0].childNodes[0].data
77 if location.getElementsByTagName('country')[0].childNodes:
78 country = location.getElementsByTagName('country')[0].childNodes[0].data
79 if location.getElementsByTagName('postalcode')[0].childNodes:
80 postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
81 return '\n'.join([street, city, country, postalcode])
83 def parse_date(self, date_string):
84 """ Parse date string into datetime object """
85 fmt = "%a, %d %b %Y %H:%M:%S"
86 result = time.strptime(date_string, fmt)
87 return datetime(result.tm_year,
94 class LocationUpdater:
99 self.loop = gobject.MainLoop()
101 self.control = location.GPSDControl.get_default()
102 self.control.set_properties(preferred_method=location.METHOD_GNSS,
103 preferred_interval=location.INTERVAL_DEFAULT)
104 self.control.connect("error-verbose", self.on_error, self.loop)
105 self.control.connect("gpsd-stopped", self.on_stop, self.loop)
107 self.device = location.GPSDevice()
108 self.device.connect("changed", self.on_changed, self.control)
110 def update_location(self):
111 """ Run the loop and update lat and long """
113 gobject.idle_add(self.start_location, self.control)
116 def on_error(self, control, error, data):
117 """ Handle errors """
118 print "location error: %d... quitting" % error
121 def on_changed(self, device, data):
122 """ Set long and lat """
126 # once fix is found and long, lat available set long lat
127 if device.fix[1] & location.GPS_DEVICE_LATLONG_SET:
128 self.lat, self.long = device.fix[4:6]
131 def on_stop(self, control, data):
132 """ Stop the location service """
136 def start_location(self, data):
137 """ Start the location service """
142 """ Reset coordinates """
145 self.device.reset_last_known()
152 self.url_base = "http://ws.audioscrobbler.com/2.0/"
153 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
156 self.parser = GigParser()
157 self.location = LocationUpdater()
158 self.win = hildon.StackableWindow()
159 self.app_title = "Gig Finder"
160 # TODO: Add preferences for distance, refactor gui code, maybe do km
161 # to mile conversions
164 """ Build the gui and start the update thread """
165 program = hildon.Program.get_instance()
166 menu = self.create_menu()
168 self.win.set_title(self.app_title)
169 self.win.connect("destroy", gtk.main_quit, None)
170 self.win.set_app_menu(menu)
172 Thread(target=self.update_gigs).start()
177 def show_about(self, widget, data):
178 """ Show about dialog """
179 dialog = gtk.AboutDialog()
180 dialog.set_name('Gig Finder')
181 dialog.set_version(__version__)
182 dialog.set_authors(__authors__)
183 dialog.set_comments('Display gigs close by.\nUsing the http://www.last.fm api.')
184 dialog.set_license('Distributed under the MIT license.\nhttp://www.opensource.org/licenses/mit-license.php')
185 dialog.set_copyright(__copyright__)
188 def update(self, widget, data):
189 """ Start update process """
190 self.win.set_title(self.app_title)
191 self.location.reset()
192 self.win.remove(self.pannable_area)
193 Thread(target=self.update_gigs).start()
195 def update_gigs(self):
197 gobject.idle_add(self.show_message, "Getting events")
198 gobject.idle_add(self.location.update_location)
201 while not self.location.lat or not self.location.long:
204 events = self.get_events(self.location.lat, self.location.long)
205 gobject.idle_add(self.hide_message)
206 gobject.idle_add(self.show_events, events)
209 def show_message(self, message):
210 """ Set window progress indicator and show message """
211 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
212 self.banner = hildon.hildon_banner_show_information(self.win,
216 def hide_message(self):
217 """ Hide banner and sete progress indicator """
219 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
221 def get_events(self, lat, long):
222 """ Retrieve xml and parse into events list """
223 xml = self.get_xml(lat, long)
224 events = self.parser.parse_xml(xml,
229 def show_events(self, events):
230 """ Sort events, set new window title and add events to table """
232 events = self.sort_gigs(events)
233 self.win.set_title('%s (%s)' % (self.app_title, len(events)))
234 self.add_events(events)
236 label = gtk.Label('No events available')
237 vbox = gtk.VBox(False, 0)
238 vbox.pack_start(label, True, True, 0)
242 def distance_cmp(self, x, y):
243 """ Compare distances for list sort """
251 def sort_gigs(self, events):
252 """ Sort gig by distance """
253 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
256 def get_xml(self, lat, long):
257 """ Return xml from lastfm """
258 method = "geo.getevents"
259 params = urllib.urlencode({'method': method,
260 'api_key': self.api_key,
261 'distance': self.distance,
264 response = urllib.urlopen(self.url_base, params)
265 return response.read()
267 def create_menu(self):
268 """ Build application menu """
269 update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
270 update_button.set_label('Update')
271 update_button.connect('clicked',
275 about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
276 about_button.set_label('About')
277 about_button.connect('clicked',
281 menu = hildon.AppMenu()
282 menu.append(update_button)
283 menu.append(about_button)
287 def show_details(self, widget, data):
288 """ Open new window showing gig details """
289 win = hildon.StackableWindow()
290 win.set_title(data['title'])
292 win.vbox = gtk.VBox()
295 scroll = hildon.PannableArea()
296 win.vbox.pack_start(scroll, True, True, 0)
298 view = hildon.TextView()
299 view.set_editable(False)
300 view.unset_flags(gtk.CAN_FOCUS)
301 view.set_wrap_mode(gtk.WRAP_WORD)
302 buffer = view.get_buffer()
303 end = buffer.get_end_iter()
304 buffer.insert(end, '%s\n' % data['title'])
305 buffer.insert(end, 'Artists: %s\n' % data['artists'])
306 buffer.insert(end, 'Venue: %s\n' % data['venue'])
307 buffer.insert(end, '%s\n' % data['address'])
308 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M'))
309 buffer.insert(end, '\n')
310 scroll.add_with_viewport(view)
315 """ Add table for events """
316 self.table = gtk.Table(columns=1)
317 self.table.set_row_spacings(10)
318 self.table.set_col_spacings(10)
320 self.pannable_area = hildon.PannableArea()
321 self.pannable_area.add_with_viewport(self.table)
322 self.pannable_area.show_all()
323 self.win.add(self.pannable_area)
325 def add_events(self, events):
326 """ Add a table of buttons """
330 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
331 hildon.BUTTON_ARRANGEMENT_VERTICAL)
332 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
333 button.connect("clicked", self.show_details, event)
334 self.table.attach(button, 0, 1, pos, pos+1)
336 self.table.show_all()
338 if __name__ == "__main__":