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
23 from threading import Thread
26 from locator import LocationUpdater
28 gtk.gdk.threads_init()
32 def parse_xml(self, xml, lat, long):
33 """ Parse xml into a dict """
36 dom = parseString(xml)
38 events = dom.getElementsByTagName('event')
40 start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
41 if start_date.date() == today:
42 title = event.getElementsByTagName('title')[0].childNodes[0].data
44 artists_element = event.getElementsByTagName('artists')[0]
46 for artist in artists_element.getElementsByTagName('artist'):
47 artist_list.append(artist.childNodes[0].data)
48 artists = ', '.join(artist_list)
50 venue_details = event.getElementsByTagName('venue')[0]
51 venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
52 address = self.get_address(venue_details.getElementsByTagName('location')[0])
53 geo_data = venue_details.getElementsByTagName('geo:point')[0]
54 venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
55 venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
56 distance = location.distance_between(float(lat),
61 events_list.append({'title': title,
69 def get_address(self, location):
70 """ Return the venues address details from the xml element """
75 if location.getElementsByTagName('street')[0].childNodes:
76 street = location.getElementsByTagName('street')[0].childNodes[0].data
77 if location.getElementsByTagName('city')[0].childNodes:
78 city = location.getElementsByTagName('city')[0].childNodes[0].data
79 if location.getElementsByTagName('country')[0].childNodes:
80 country = location.getElementsByTagName('country')[0].childNodes[0].data
81 if location.getElementsByTagName('postalcode')[0].childNodes:
82 postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
83 return '\n'.join([street, city, country, postalcode])
85 def parse_date(self, date_string):
86 """ Parse date string into datetime object """
87 fmt = "%a, %d %b %Y %H:%M:%S"
88 result = time.strptime(date_string, fmt)
89 return datetime(result.tm_year,
100 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
101 self.url_base = "http://ws.audioscrobbler.com/2.0/"
102 self.parser = GigParser()
104 def get_events(self, lat, long, distance):
105 """ Retrieve xml and parse into events list """
107 for page in ['1', '2', '3']:
108 xml = self.get_xml(lat, long, distance, page=page)
109 events.extend(self.parser.parse_xml(xml,
112 return self.sort_events(events)
114 def sort_events(self, events):
115 """ Sort gig by distance """
116 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
119 def get_xml(self, lat, long, distance, page="1"):
120 """ Return xml from lastfm """
121 method = "geo.getevents"
122 params = urllib.urlencode({'method': method,
123 'api_key': self.api_key,
124 'distance': distance,
128 response = urllib.urlopen(self.url_base, params)
129 return response.read()
131 def distance_cmp(self, x, y):
132 """ Compare distances for list sort """
148 self.location = LocationUpdater()
149 self.events = Events()
150 self.win = hildon.StackableWindow()
151 self.app_title = "Gig Finder"
153 # Add user settings for distance, date
155 # maybe do km to mile conversions
158 """ Build the gui and start the update thread """
159 program = hildon.Program.get_instance()
160 menu = self.create_menu()
162 self.win.set_title(self.app_title)
163 self.win.connect("destroy", gtk.main_quit, None)
164 self.win.set_app_menu(menu)
166 Thread(target=self.update_gigs).start()
171 def show_about(self, widget, data):
172 """ Show about dialog """
173 dialog = gtk.AboutDialog()
174 dialog.set_name('Gig Finder')
175 dialog.set_version(__version__)
176 dialog.set_authors(__authors__)
177 dialog.set_comments('Display gigs close by.\nUsing the http://www.last.fm api.')
178 dialog.set_license('Distributed under the MIT license.\nhttp://www.opensource.org/licenses/mit-license.php')
179 dialog.set_copyright(__copyright__)
182 def update(self, widget, data):
183 """ Start update process """
184 self.win.set_title(self.app_title)
185 self.location.reset()
186 self.win.remove(self.pannable_area)
187 Thread(target=self.update_gigs).start()
189 def update_gigs(self):
191 gobject.idle_add(self.show_message, "Getting events")
193 if not 'applications' in os.path.abspath(__file__):
194 gobject.idle_add(self.location.update_location)
197 # TODO: needs a timeout
198 while not self.location.lat or not self.location.long:
201 self.location.lat = float(51.517369)
202 self.location.long = float(-0.082998)
204 events = self.events.get_events(self.location.lat,
207 gobject.idle_add(self.hide_message)
208 gobject.idle_add(self.show_events, events)
211 def show_message(self, message):
212 """ Set window progress indicator and show message """
213 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
214 self.banner = hildon.hildon_banner_show_information(self.win,
218 def hide_message(self):
219 """ Hide banner and sete progress indicator """
221 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
223 def show_events(self, events):
224 """ Sort events, set new window title and add events to table """
226 self.win.set_title('%s (%s)' % (self.app_title, len(events)))
227 self.add_events(events)
229 label = gtk.Label('No events available')
230 vbox = gtk.VBox(False, 0)
231 vbox.pack_start(label, True, True, 0)
235 def create_menu(self):
236 """ Build application menu """
237 update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
238 update_button.set_label('Update')
239 update_button.connect('clicked',
243 about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
244 about_button.set_label('About')
245 about_button.connect('clicked',
249 menu = hildon.AppMenu()
250 menu.append(update_button)
251 menu.append(about_button)
255 def show_details(self, widget, data):
256 """ Open new window showing gig details """
257 win = hildon.StackableWindow()
258 win.set_title(data['title'])
260 win.vbox = gtk.VBox()
263 scroll = hildon.PannableArea()
264 win.vbox.pack_start(scroll, True, True, 0)
266 view = hildon.TextView()
267 view.set_editable(False)
268 view.unset_flags(gtk.CAN_FOCUS)
269 view.set_wrap_mode(gtk.WRAP_WORD)
270 buffer = view.get_buffer()
271 end = buffer.get_end_iter()
272 buffer.insert(end, '%s\n' % data['title'])
273 buffer.insert(end, 'Artists: %s\n' % data['artists'])
274 buffer.insert(end, 'Venue: %s\n' % data['venue'])
275 buffer.insert(end, '%s\n' % data['address'])
276 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M %d/%M/%Y'))
277 buffer.insert(end, '\n')
278 scroll.add_with_viewport(view)
283 """ Add table for events """
284 self.table = gtk.Table(columns=1)
285 self.table.set_row_spacings(10)
286 self.table.set_col_spacings(10)
288 self.pannable_area = hildon.PannableArea()
289 self.pannable_area.add_with_viewport(self.table)
290 self.pannable_area.show_all()
291 self.win.add(self.pannable_area)
293 def add_events(self, events):
294 """ Add a table of buttons """
299 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
300 hildon.BUTTON_ARRANGEMENT_VERTICAL)
301 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
302 button.connect("clicked", self.show_details, event)
303 self.table.attach(button, 0, 1, pos, pos+1, yoptions=gtk.FILL)
305 self.table.show_all()
307 if __name__ == "__main__":