c7b8c8653c189e876f498032944e7c3ab8f7567e
[hermes] / package / src / org / maemo / hermes / gui / gtkui.py
1 import gettext
2 import gtk, gobject
3 import traceback
4 import time
5 import thread
6 import urllib2
7 import hildon
8 import conic
9 import webbrowser
10 from org.bleb.wimpworks import WimpWorks
11 from org.maemo.hermes.gui.contactview import ContactView
12 from org.maemo.hermes.gui.mapcontact import MapContact
13 from org.maemo.hermes.gui.accountsdialogue import AccountsDialogue
14 from org.bleb.wimpworks import HildonMainScreenLayout
15 from org.maemo.hermes.engine.hermes import Hermes
16 from org.maemo.hermes.engine.service import CredentialsExpiredException
17
18 class HermesGUI(WimpWorks):
19     """Provides the GUI for Hermes, allowing the syncing of Facebook and
20        Twitter friends' information with the Evolution contacts' database.
21        
22        Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
23        Released under the Artistic Licence."""
24
25
26     # -----------------------------------------------------------------------
27     def __init__(self, providers = None):
28         gettext.install('hermes','/opt/hermes/share/locale/')
29         WimpWorks.__init__(self, 'Hermes', version = '0.8.7', dbus_name = 'org.maemo.hermes')
30         self.set_background('background.png')
31         
32         layout = HildonMainScreenLayout(offset = 0.8, container = self)
33         layout.add_button('Retrieve', _("Get contacts' missing info"))
34         layout.add_button('Refresh', _("Update contacts' info"))
35         
36         self.add_menu_action("Accounts")
37         self.add_menu_action("About")
38         self.menu.show_all()
39         
40         self.providers = providers
41         self.progressnote = None
42         connection = conic.Connection()
43         gobject.timeout_add(100, connection.request_connection, conic.CONNECT_FLAG_NONE)
44
45   
46     # -----------------------------------------------------------------------
47     def do_retrieve(self, widget):
48         self.sync(widget, False)
49     
50     
51     # -----------------------------------------------------------------------
52     def do_refresh(self, widget):
53         self.sync(widget, True)
54
55
56     # -----------------------------------------------------------------------
57     def do_accounts(self, widget = None):
58         dialog = AccountsDialogue(self.main_window, self.providers)
59         dialog.show()
60
61
62     # -----------------------------------------------------------------------
63     def do_about(self, widget):
64         """Inspired by HeAboutDialog Python port:
65            http://wiki.maemo.org/Hildon-Extras#HeAboutDialog"""
66
67         dlg = gtk.Dialog(_("About"), self.main_window)
68
69         icon = gtk.Image()
70         icon.set_from_icon_name(self.name.lower(), gtk.ICON_SIZE_DIALOG)
71         icon.set_padding(5, 5)
72
73         name = gtk.Label(self.name)
74         name.set_alignment(0, 1)
75         hildon.hildon_helper_set_logical_font(name, 'X-LargeSystemFont')
76
77         version = gtk.Label('v%s' % (self.version))
78         version.set_alignment(0, 1)
79         version.set_padding(10, 0)
80         hildon.hildon_helper_set_logical_font(version, 'LargeSystemFont')
81         
82         desc = gtk.Label(_("Enrich contacts' from social networks."))
83         desc.set_alignment(0, 0)
84
85         copy = gtk.Label("Copyright (c) 2010 Andrew Flegg, Fredrik Wendt, Tim Samoff")
86         copy.set_alignment(0, 1)
87         copy.set_padding(0, 5)
88         hildon.hildon_helper_set_logical_font(copy, 'SmallSystemFont')
89         hildon.hildon_helper_set_logical_color(copy, gtk.RC_FG, gtk.STATE_NORMAL, "SecondaryTextColor")
90
91         layout = gtk.Table(3, 3, False)
92         layout.attach(icon, 0, 1, 0, 2, 0, gtk.EXPAND, 0, 0)
93         layout.attach(name, 1, 2, 0, 1, 0, gtk.EXPAND | gtk.FILL, 0, 0)
94         layout.attach(version, 2, 3, 0, 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
95         layout.attach(desc, 1, 3, 1, 2, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
96         layout.attach(copy, 0, 3, 2, 3, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
97         dlg.vbox.add(layout)
98
99         dlg.add_button(_('Visit website'), gtk.RESPONSE_APPLY)
100         dlg.add_button(_('Report bug'), gtk.RESPONSE_NO)
101         dlg.show_all()
102         response = dlg.run()
103         if response == gtk.RESPONSE_APPLY:
104             webbrowser.open('http://hermes.garage.maemo.org/')
105         elif response == gtk.RESPONSE_NO:
106             webbrowser.open('https://bugs.maemo.org/enter_bug.cgi?product=Hermes')
107         dlg.hide()
108    
109
110     # -----------------------------------------------------------------------
111     def sync(self, widget, force, main = True):
112         enabled = []
113         for provider in self.providers:
114             if self.gconf.get_bool('/apps/maemo/hermes/use_%s' % (provider.get_id())):
115                 enabled.append(provider)
116                 
117         if main and len(enabled) == 0:
118             saved = self.do_accounts()
119             if saved == gtk.RESPONSE_DELETE_EVENT:
120                 return
121             
122         print "doing sync", main
123         
124         if main:
125             self.main_window.set_property('sensitive', False)
126             thread.start_new_thread(self.sync, (widget, force, False))
127         else:
128             try:
129                 self.progress('', 0, 0)
130                 services = []
131                 for provider in enabled:
132                     services.append(provider.service(self))
133                     
134                 hermes = Hermes(services, self.progress)
135                 hermes.run(force)
136                 gobject.idle_add(self.open_summary, hermes)
137         
138             except urllib2.HTTPError, e:
139                 traceback.print_exc()
140                 if e.code == 401:
141                     gobject.idle_add(self.report_error, _('Authentication problem. Check credentials.'), True)
142                 else:
143                     gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
144         
145             except urllib2.URLError, e:
146                 traceback.print_exc()
147                 gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
148                 
149             except CredentialsExpiredException, e:
150                 gobject.idle_add(self.report_error, _('Credentials expired. Please reauthorise %s.') % (e.message))
151           
152             except Exception, e:
153                 traceback.print_exc()
154                 gobject.idle_add(self.report_error, _('Something went wrong: ') + e.message)
155     
156     
157     # -----------------------------------------------------------------------
158     def open_summary(self, fb2c):
159         gobject.idle_add(self.main_window.set_property, 'sensitive', True)
160     
161         dialog = gtk.Dialog(_('Summary'), self.main_window)
162         dialog.add_button(_('Done'), gtk.RESPONSE_OK)
163       
164         button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
165                                title = _('Updated %d contacts') % (len(fb2c.updated)))
166         button.connect('clicked', self.show_contacts, fb2c, fb2c.updated)
167         button.set_property('sensitive', len(fb2c.updated) > 0)
168         dialog.vbox.add(button)
169         
170         button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
171                                title = _('Matched %d contacts') % (len(fb2c.matched)))
172         button.connect('clicked', self.show_contacts, fb2c, fb2c.matched)
173         button.set_property('sensitive', len(fb2c.matched) > 0)
174         dialog.vbox.add(button)
175       
176         button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
177                                title = _('%d contacts unmatched') % (len(fb2c.unmatched)))
178         button.connect('clicked', self.show_contacts, fb2c, fb2c.unmatched)
179         button.set_property('sensitive', len(fb2c.unmatched) > 0)
180         dialog.vbox.add(button)
181       
182         dialog.show_all()
183         dialog.run()
184         dialog.hide()
185     
186     
187     # -----------------------------------------------------------------------
188     def show_contacts(self, widget, fb2c, contacts):
189         view = ContactView(contacts)
190     
191         dialog = gtk.Dialog(_('Contacts'), self.main_window)
192         view.connect('contact-activated', self.map_contact, fb2c)
193         dialog.vbox.add(view)
194         dialog.show_all()
195         dialog.vbox.add(view.get_search())
196       
197         dialog.run()
198         dialog.hide()
199       
200       
201     # -----------------------------------------------------------------------
202     def map_contact(self, widget, contact, fb2c):
203         view = MapContact(fb2c.friends, contact)
204
205         dialog = gtk.Dialog(contact.get_name(), self.main_window)
206         dialog.add_button(_('Update'), gtk.RESPONSE_OK)
207         dialog.vbox.add(view)
208         dialog.show_all()
209         dialog.vbox.add(view.get_search())
210       
211         result = dialog.run()
212         dialog.hide()
213         if result == gtk.RESPONSE_OK:
214             friend = view.get_selected_friend()
215             if friend:
216                 if friend.get_contact() == contact:
217                     hildon.hildon_banner_show_information(self.main_window, '', _("Removing existing mappings is not yet supported"))
218                 elif view.contact_mapped:
219                     fb2c.update_contact(contact, friend, True, True)
220                     widget.add_mapping(friend.get_source())
221                 else:
222                     fb2c.update_contact(contact, friend, False, True)
223                     widget.add_mapping(friend.get_source())
224     
225                         
226     # -----------------------------------------------------------------------
227     def need_auth(self, main = False):
228         """Part of the GUI callback API."""
229         
230         if main:
231             hildon.hildon_banner_show_information(self.main_window, '', _("Need to authenticate via browser"))
232         else:
233             gobject.idle_add(self.need_auth, True)
234       
235     
236     # -----------------------------------------------------------------------
237     def block_for_auth(self, prompt = False, main = False, lock = None):
238         """Part of the GUI callback API."""
239
240         if main:
241             note = gtk.Dialog(_('Service authorisation'), self.main_window)
242             note.add_button(_("Validate"), gtk.RESPONSE_OK)
243             if prompt:
244                 input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
245                 input.set_property('is-focus', False)
246                 note.set_title(_("Verification code from web browser"))
247                 note.vbox.add(input)
248             else:
249                 note.vbox.add(gtk.Label(_("\nPress 'Validate' once account has\nbeen authorised in web browser.\n")))
250     
251             note.show_all()
252             result = note.run()
253             note.hide()
254             lock.release()
255             if prompt and result == gtk.RESPONSE_OK:
256                 print input.get_text()
257                 return input.get_text()
258             else:
259                 return None
260         
261         else:
262             time.sleep(2)
263             lock = thread.allocate_lock()
264             lock.acquire()
265             gobject.idle_add(self.block_for_auth, prompt, True, lock)
266             lock.acquire()
267             lock.release()
268                       
269                         
270     # -----------------------------------------------------------------------
271     def progress(self, msg, i, j, main = False):
272         """Part of the GUI callback API."""
273
274         if main:
275             if i == 0:
276                 self.progressbar = gtk.ProgressBar()
277                 self.progressnote = gtk.Dialog(_("Initialising connections"), self.main_window)
278                 self.progressnote.vbox.add(self.progressbar)
279                 hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1)
280                
281                 self.progressnote.show_all()
282               
283             elif i < j:
284                 self.progressnote.set_title(_(msg))
285                 hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0)
286                 
287                 self.progressbar.set_fraction(float(i) / float(j))
288               
289             else:
290                 self.progressnote.destroy()
291               
292             print msg, i,j
293         else:
294             gobject.idle_add(self.progress, msg, i, j, True)
295
296        
297     # -----------------------------------------------------------------------
298     def report_error(self, e, prefs = False):
299         if self.progressnote:
300             self.main_window.set_property('sensitive', True)
301             self.progressnote.destroy()
302     
303         hildon.hildon_banner_show_information(self.main_window, '', e)
304         if prefs:
305             self.do_accounts()