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 """
33 dom = parseString(xml)
35 events = dom.getElementsByTagName('event')
37 start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
38 if start_date.date() == today:
39 title = event.getElementsByTagName('title')[0].childNodes[0].data
41 artists_element = event.getElementsByTagName('artists')[0]
43 for artist in artists_element.getElementsByTagName('artist'):
44 artist_list.append(artist.childNodes[0].data)
45 artists = ', '.join(artist_list)
47 venue_details = event.getElementsByTagName('venue')[0]
48 venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
49 address = self.get_address(venue_details.getElementsByTagName('location')[0])
50 geo_data = venue_details.getElementsByTagName('geo:point')[0]
51 venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
52 venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
53 distance = location.distance_between(float(lat),
58 events_list.append({'title': title,
66 def get_address(self, location):
67 """ Return the venues address details from the xml element """
72 if location.getElementsByTagName('street')[0].childNodes:
73 street = location.getElementsByTagName('street')[0].childNodes[0].data
74 if location.getElementsByTagName('city')[0].childNodes:
75 city = location.getElementsByTagName('city')[0].childNodes[0].data
76 if location.getElementsByTagName('country')[0].childNodes:
77 country = location.getElementsByTagName('country')[0].childNodes[0].data
78 if location.getElementsByTagName('postalcode')[0].childNodes:
79 postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
80 return '\n'.join([street, city, country, postalcode])
82 def parse_date(self, date_string):
83 """ Parse date string into datetime object """
84 fmt = "%a, %d %b %Y %H:%M:%S"
85 result = time.strptime(date_string, fmt)
86 return datetime(result.tm_year,
93 class LocationUpdater:
98 self.loop = gobject.MainLoop()
100 self.control = location.GPSDControl.get_default()
101 self.control.set_properties(preferred_method=location.METHOD_AGNSS,
102 preferred_interval=location.INTERVAL_DEFAULT)
103 self.control.connect("error-verbose", self.on_error, self.loop)
104 self.control.connect("gpsd-stopped", self.on_stop, self.loop)
106 self.device = location.GPSDevice()
107 self.device.connect("changed", self.on_changed, self.control)
109 def update_location(self):
110 """ Run the loop and update lat and long """
112 gobject.idle_add(self.start_location, self.control)
115 def on_error(self, control, error, data):
116 """ Handle errors """
117 print "location error: %d... quitting" % error
120 def on_changed(self, device, data):
121 """ Set long and lat """
125 # once fix is found and long, lat available set long lat
126 if device.fix[1] & location.GPS_DEVICE_LATLONG_SET:
127 self.lat, self.long = device.fix[4:6]
130 def on_stop(self, control, data):
131 """ Stop the location service """
135 def start_location(self, data):
136 """ Start the location service """
141 """ Reset coordinates """
144 self.device.reset_last_known()
151 self.url_base = "http://ws.audioscrobbler.com/2.0/"
152 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
155 self.parser = GigParser()
156 self.location = LocationUpdater()
157 self.win = hildon.StackableWindow()
158 self.app_title = "Gig Finder"
160 # Add user settings for distance, date
162 # maybe do km to mile conversions
165 """ Build the gui and start the update thread """
166 program = hildon.Program.get_instance()
167 menu = self.create_menu()
169 self.win.set_title(self.app_title)
170 self.win.connect("destroy", gtk.main_quit, None)
171 self.win.set_app_menu(menu)
173 Thread(target=self.update_gigs).start()
178 def show_about(self, widget, data):
179 """ Show about dialog """
180 dialog = gtk.AboutDialog()
181 dialog.set_name('Gig Finder')
182 dialog.set_version(__version__)
183 dialog.set_authors(__authors__)
184 dialog.set_comments('Display gigs close by.\nUsing the http://www.last.fm api.')
185 dialog.set_license('Distributed under the MIT license.\nhttp://www.opensource.org/licenses/mit-license.php')
186 dialog.set_copyright(__copyright__)
189 def update(self, widget, data):
190 """ Start update process """
191 self.win.set_title(self.app_title)
192 self.location.reset()
193 self.win.remove(self.pannable_area)
194 Thread(target=self.update_gigs).start()
196 def update_gigs(self):
198 gobject.idle_add(self.show_message, "Getting events")
199 gobject.idle_add(self.location.update_location)
202 while not self.location.lat or not self.location.long:
205 events = self.get_events(self.location.lat, self.location.long)
206 gobject.idle_add(self.hide_message)
207 gobject.idle_add(self.show_events, events)
210 def show_message(self, message):
211 """ Set window progress indicator and show message """
212 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
213 self.banner = hildon.hildon_banner_show_information(self.win,
217 def hide_message(self):
218 """ Hide banner and sete progress indicator """
220 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
222 def get_events(self, lat, long):
223 """ Retrieve xml and parse into events list """
224 xml = self.get_xml(lat, long)
225 events = self.parser.parse_xml(xml,
230 def show_events(self, events):
231 """ Sort events, set new window title and add events to table """
233 events = self.sort_gigs(events)
234 self.win.set_title('%s (%s)' % (self.app_title, len(events)))
235 self.add_events(events)
237 label = gtk.Label('No events available')
238 vbox = gtk.VBox(False, 0)
239 vbox.pack_start(label, True, True, 0)
243 def distance_cmp(self, x, y):
244 """ Compare distances for list sort """
252 def sort_gigs(self, events):
253 """ Sort gig by distance """
254 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
257 def get_xml(self, lat, long):
258 """ Return xml from lastfm """
259 method = "geo.getevents"
260 params = urllib.urlencode({'method': method,
261 'api_key': self.api_key,
262 'distance': self.distance,
265 response = urllib.urlopen(self.url_base, params)
266 return response.read()
268 def create_menu(self):
269 """ Build application menu """
270 update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
271 update_button.set_label('Update')
272 update_button.connect('clicked',
276 about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
277 about_button.set_label('About')
278 about_button.connect('clicked',
282 menu = hildon.AppMenu()
283 menu.append(update_button)
284 menu.append(about_button)
288 def show_details(self, widget, data):
289 """ Open new window showing gig details """
290 win = hildon.StackableWindow()
291 win.set_title(data['title'])
293 win.vbox = gtk.VBox()
296 scroll = hildon.PannableArea()
297 win.vbox.pack_start(scroll, True, True, 0)
299 view = hildon.TextView()
300 view.set_editable(False)
301 view.unset_flags(gtk.CAN_FOCUS)
302 view.set_wrap_mode(gtk.WRAP_WORD)
303 buffer = view.get_buffer()
304 end = buffer.get_end_iter()
305 buffer.insert(end, '%s\n' % data['title'])
306 buffer.insert(end, 'Artists: %s\n' % data['artists'])
307 buffer.insert(end, 'Venue: %s\n' % data['venue'])
308 buffer.insert(end, '%s\n' % data['address'])
309 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M %d/%M/%Y'))
310 buffer.insert(end, '\n')
311 scroll.add_with_viewport(view)
316 """ Add table for events """
317 self.table = gtk.Table(columns=1)
318 self.table.set_row_spacings(10)
319 self.table.set_col_spacings(10)
321 self.pannable_area = hildon.PannableArea()
322 self.pannable_area.add_with_viewport(self.table)
323 self.pannable_area.show_all()
324 self.win.add(self.pannable_area)
326 def add_events(self, events):
327 """ Add a table of buttons """
331 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
332 hildon.BUTTON_ARRANGEMENT_VERTICAL)
333 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
334 button.connect("clicked", self.show_details, event)
335 self.table.attach(button, 0, 1, pos, pos+1)
337 self.table.show_all()
339 if __name__ == "__main__":