Big update to 0.1.0. Improved error handling, syncing, the works...
[hermes] / package / src / gui.py
1 #!/usr/bin/env python
2
3 import gtk, gobject
4 import gnome.gconf
5 import hildon, osso
6 import traceback
7 import time
8 import thread
9 import contactview
10 import urllib2  
11 from hermes import Hermes
12
13 gobject.threads_init()
14
15 class HermesGUI:
16   """Provides the GUI for Hermes, allowing the syncing of Facebook and
17      Twitter friends' information with the Evolution contacts' database.
18        
19      Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
20      Released under the Artistic Licence."""
21
22
23   # -----------------------------------------------------------------------
24   def __init__(self):
25     """Constructor. Initialises the gconf connection."""
26     self.gc = gnome.gconf.client_get_default()
27
28
29   # -----------------------------------------------------------------------
30   def doSync(self, widget, force, main = True):
31     if main and not self.get_use_facebook() and not self.get_use_twitter():
32       saved = self.open_prefs()
33       if saved == gtk.RESPONSE_DELETE_EVENT:
34         return
35
36     if main:
37       self.window.set_property('sensitive', False)
38       thread.start_new_thread(self.doSync, (widget, force, False))
39     else:
40       try:
41         fb2c = Hermes(self,
42                       twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None,
43                       facebook = self.get_use_facebook(),
44                       empty = self.get_create_empty())
45         fb2c.load_friends()
46         fb2c.sync_contacts(resync = force)
47         gobject.idle_add(self.open_summary, fb2c)
48
49       except urllib2.HTTPError, e:
50         traceback.print_exc()
51         if e.code == 401:
52           gobject.idle_add(self.report_error, 'Authentication problem. Check credentials.', True)
53         else:
54           gobject.idle_add(self.report_error, 'Network connection error. Check connectivity.')
55
56       except urllib2.URLError, e:
57         traceback.print_exc()
58         gobject.idle_add(self.report_error, 'Network connection error. Check connectivity.')
59       
60       except Exception, e:
61         traceback.print_exc()
62         gobject.idle_add(self.report_error, 'Something went wrong: ' + e.message)
63
64
65   # -----------------------------------------------------------------------
66   def open_summary(self, fb2c):
67     gobject.idle_add(self.window.set_property, 'sensitive', True)
68
69     dialog = gtk.Dialog('Summary', self.window)
70     dialog.add_button('Done', gtk.RESPONSE_OK)
71     
72     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
73                            title = 'Updated %d contacts' % (len(fb2c.updated)))
74     button.connect('clicked', self.show_contacts, fb2c.updated)
75     button.set_property('sensitive', len(fb2c.updated) > 0)
76     dialog.vbox.add(button)
77     
78     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
79                            title = 'Matched %d contacts' % (len(fb2c.matched)))
80     button.connect('clicked', self.show_contacts, fb2c.matched)
81     button.set_property('sensitive', len(fb2c.matched) > 0)
82     dialog.vbox.add(button)
83     
84     button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
85                            title = '%d contacts unmatched' % (len(fb2c.unmatched)))
86     button.connect('clicked', self.show_contacts, fb2c.unmatched)
87     button.set_property('sensitive', len(fb2c.unmatched) > 0)
88     dialog.vbox.add(button)
89     
90     dialog.show_all()
91     dialog.run()
92     dialog.hide()
93   
94
95   # -----------------------------------------------------------------------
96   def show_contacts(self, widget, contacts):
97     view = contactview.ContactView(contacts)
98
99     dialog = gtk.Dialog('Contacts', self.window)
100     #view.connect('contact-activated', self.map_contact)
101     dialog.vbox.add(view)
102     dialog.show_all()
103     
104     dialog.run()
105     dialog.hide()
106     
107     
108   # -----------------------------------------------------------------------
109   def map_contact(self, widget, contact):
110     print widget, contact
111
112
113   # -----------------------------------------------------------------------
114   def open_prefs(self, widget = None):
115     dialog = gtk.Dialog('Accounts', self.window)
116     dialog.add_button('Save', gtk.RESPONSE_OK)
117
118     #pa = hildon.PannableArea()
119     #dialog.vbox.add(pa)
120     content = dialog.vbox 
121     #content = gtk.VBox()
122     #pa.add(content)
123     #pa.set_size_request(600, 380)
124
125     use_facebook = self.new_checkbox('Use Facebook', content)
126     use_facebook.set_active(self.get_use_facebook())
127
128     indent = self.new_indent(content)
129     fb_msg = self.add_linked(indent, use_facebook, gtk.Label('Note: authentication via web page'))
130     fb_msg.set_property('justify', gtk.JUSTIFY_LEFT)
131     
132     fb_empty = self.add_linked(indent, use_facebook, self.new_checkbox('Create birthday-only contacts'))
133     fb_empty.set_active(self.get_create_empty())
134
135     use_twitter = self.new_checkbox('Use Twitter', content)
136     use_twitter.set_active(self.get_use_twitter())
137
138     indent = self.new_indent(content)
139     tw_user = self.add_linked(indent, use_twitter, self.new_input('Twitter username'))
140     tw_user.set_text(self.get_twitter_credentials()[0])
141
142     tw_pass = self.add_linked(indent, use_twitter, self.new_input('Twitter password'))
143     tw_pass.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL |
144                                               gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
145     tw_pass.set_text(self.get_twitter_credentials()[1])
146
147     dialog.show_all()
148     result = dialog.run()
149     dialog.hide()
150     if result == gtk.RESPONSE_OK:
151       self.set_use_facebook(use_facebook.get_active())
152       self.set_create_empty(fb_empty.get_active())
153       self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text())
154
155     return result
156  
157   # -----------------------------------------------------------------------
158   def new_checkbox(self, label, box = None):
159     checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
160     checkbox.set_label(label)
161     if box:
162       box.add(checkbox)
163     return checkbox
164
165
166   # -----------------------------------------------------------------------
167   def new_indent(self, box):
168     outer = gtk.HBox()
169     indent = gtk.VBox()
170     outer.pack_start(indent, padding=48)
171     box.add(outer)
172     return indent
173
174  
175   # -----------------------------------------------------------------------
176   def new_input(self, text, box = None):
177     input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
178     input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
179     input.set_placeholder(text)
180     input.set_property('is-focus', False)
181     if box:
182       box.add(input)
183     return input
184
185
186   # -----------------------------------------------------------------------
187   def add_linked(self, box, ctrl, to_add):
188     box.add(to_add)
189     self.sync_edit(ctrl, to_add)
190     ctrl.connect('toggled', self.sync_edit, to_add)
191     return to_add
192   
193     
194   # -----------------------------------------------------------------------
195   def sync_edit(self, use_twitter, edit):
196     edit.set_property('sensitive', use_twitter.get_active())
197
198
199   # -----------------------------------------------------------------------
200   def need_auth(self, main = False):
201     if main:
202       hildon.hildon_banner_show_information(self.window, '', "Need to authenticate with Facebook")
203     else:
204       gobject.idle_add(self.need_auth, True)
205     
206
207   # -----------------------------------------------------------------------
208   def block_for_auth(self, main = False, lock = None):
209     if main:
210       note = gtk.Dialog('Facebook authorisation', self.window)
211       note.add_button("Validate", gtk.RESPONSE_OK)
212       note.vbox.add(gtk.Label("\nPress 'Validate' once Facebook has\nbeen authenticated in web browser.\n"))
213
214       note.show_all()
215       result = note.run()
216       note.hide()
217       lock.release()
218       
219     else:
220       time.sleep(2)
221       lock = thread.allocate_lock()
222       lock.acquire()
223       gobject.idle_add(self.block_for_auth, True, lock)
224       lock.acquire()
225       lock.release()
226
227
228   # -----------------------------------------------------------------------
229   def progress(self, i, j, main = False):
230     if main:
231       if i == 0:
232         self.progressbar = gtk.ProgressBar()
233         self.progressnote = gtk.Dialog("Fetching friends' info", self.window)
234         self.progressnote.vbox.add(self.progressbar)
235         hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1)
236         
237         self.progressnote.show_all()
238         
239       elif i < j:
240         if i == 1:
241           self.progressnote.set_title("Updating contacts")
242           hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0)
243           
244         self.progressbar.set_fraction(float(i) / float(j))
245         
246       else:
247         self.progressnote.destroy()
248         
249       print i,j
250     else:
251       gobject.idle_add(self.progress, i, j, True)
252
253
254   # -----------------------------------------------------------------------
255   def report_error(self, e, prefs = False):
256     if self.progressnote:
257       self.window.set_property('sensitive', True)
258       self.progressnote.destroy()
259
260     hildon.hildon_banner_show_information(self.window, '', e)
261     if prefs:
262       self.open_prefs()
263
264
265   # -----------------------------------------------------------------------
266   def main(self):
267     # -- Window and app...
268     #
269     self.app = hildon.Program()
270     self.window = hildon.Window()
271     gtk.set_application_name('Hermes')
272     self.osso_context = osso.Context('org.maemo.hermes', '0.0.5', False)
273     self.app.add_window(self.window)
274
275     self.window.connect("delete-event", gtk.main_quit)
276
277     # -- Main window buttons...
278     #
279     box = gtk.HButtonBox()
280     self.window.add(box)
281     
282     box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
283     button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
284                            title = 'Retrieve', value = "Get contacts' missing info")
285     button.set_property('width-request', 250)
286     button.connect('clicked', self.doSync, False)
287     box.add(button)
288
289     button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
290                            title = 'Update', value = "Update contacts' info")
291     button.set_property('width-request', 250)
292     button.connect('clicked', self.doSync, True)
293     box.add(button)
294     
295     # -- Application menu...
296     #
297     menu = hildon.AppMenu()
298     button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
299     button.set_label("Accounts")
300     button.connect("clicked", self.open_prefs)
301     menu.append(button)
302     menu.show_all()
303     self.window.set_app_menu(menu)
304   
305     self.window.show_all()
306     gtk.main()
307
308
309   def get_use_facebook(self):
310     return self.gc.get_bool("/apps/maemo/hermes/use_facebook")
311
312
313   def set_use_facebook(self, value):
314     self.gc.set_bool("/apps/maemo/hermes/use_facebook", value)
315
316   def get_create_empty(self):
317     return self.gc.get_bool("/apps/maemo/hermes/create_empty")
318
319
320   def set_create_empty(self, value):
321     self.gc.set_bool("/apps/maemo/hermes/create_empty", value)
322
323
324   def get_use_twitter(self):
325     return self.gc.get_bool("/apps/maemo/hermes/use_twitter")
326
327
328   def set_use_twitter(self, value, user, password):
329     self.gc.set_bool("/apps/maemo/hermes/use_twitter", value)
330     self.gc.set_string("/apps/maemo/hermes/twitter_user", user)
331     self.gc.set_string("/apps/maemo/hermes/twitter_pwd", password)
332     
333   
334   def get_twitter_credentials(self):
335     return (self.gc.get_string("/apps/maemo/hermes/twitter_user") or '',
336             self.gc.get_string("/apps/maemo/hermes/twitter_pwd") or '')
337
338
339 # -------------------------------------------------------------------------
340 if __name__ == '__main__':
341   HermesGUI().main()
342