Putting in a lot of fixes for fremantle
[gc-dialer] / src / dc_glade.py
1 #!/usr/bin/python2.5
2
3 """
4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008  Mark Bergman bergman AT merctech DOT com
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 """
21
22
23 from __future__ import with_statement
24
25 import sys
26 import gc
27 import os
28 import threading
29 import base64
30 import ConfigParser
31 import itertools
32 import logging
33
34 import gtk
35 import gtk.glade
36
37 import constants
38 import hildonize
39 import gtk_toolbox
40
41
42 _moduleLogger = logging.getLogger("dc_glade")
43
44
45 def getmtime_nothrow(path):
46         try:
47                 return os.path.getmtime(path)
48         except Exception:
49                 return 0
50
51
52 def display_error_message(msg):
53         error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
54
55         def close(dialog, response):
56                 dialog.destroy()
57         error_dialog.connect("response", close)
58         error_dialog.run()
59
60
61 class Dialcentral(object):
62
63         _glade_files = [
64                 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
65                 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
66                 '/usr/lib/dialcentral/dialcentral.glade',
67         ]
68
69         KEYPAD_TAB = 0
70         RECENT_TAB = 1
71         MESSAGES_TAB = 2
72         CONTACTS_TAB = 3
73         ACCOUNT_TAB = 4
74
75         NULL_BACKEND = 0
76         GV_BACKEND = 2
77         BACKENDS = (NULL_BACKEND, GV_BACKEND)
78
79         def __init__(self):
80                 self._initDone = False
81                 self._connection = None
82                 self._osso = None
83                 self._clipboard = gtk.clipboard_get()
84
85                 self._credentials = ("", "")
86                 self._selectedBackendId = self.NULL_BACKEND
87                 self._defaultBackendId = self.GV_BACKEND
88                 self._phoneBackends = None
89                 self._dialpads = None
90                 self._accountViews = None
91                 self._messagesViews = None
92                 self._recentViews = None
93                 self._contactsViews = None
94                 self._alarmHandler = None
95                 self._ledHandler = None
96                 self._originalCurrentLabels = []
97
98                 for path in self._glade_files:
99                         if os.path.isfile(path):
100                                 self._widgetTree = gtk.glade.XML(path)
101                                 break
102                 else:
103                         display_error_message("Cannot find dialcentral.glade")
104                         gtk.main_quit()
105                         return
106
107                 self._window = self._widgetTree.get_widget("mainWindow")
108                 self._notebook = self._widgetTree.get_widget("notebook")
109                 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
110                 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
111
112                 self._isFullScreen = False
113                 self._app = hildonize.get_app_class()()
114                 self._window = hildonize.hildonize_window(self._app, self._window)
115                 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
116                 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
117
118                 for scrollingWidget in (
119                         'recent_scrolledwindow',
120                         'message_scrolledwindow',
121                         'contacts_scrolledwindow',
122                         "phoneSelectionMessages_scrolledwindow",
123                         "smsMessages_scrolledwindow",
124                 ):
125                         hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
126                 for scrollingWidget in (
127                         "phonetypes_scrolledwindow",
128                         "smsMessage_scrolledEntry",
129                 ):
130                         hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
131
132                 for button in (
133                         "back",
134                         "sendSmsButton",
135                         "cancelSmsButton",
136                         "sms_button",
137                         "dial_button",
138                         "select_button",
139                         "cancel_button",
140                         "minutesEntryButton",
141                         "clearcookies",
142                 ):
143                         hildonize.set_button_thumb_selectable(button)
144
145                 replacementButtons = [gtk.Button("Test")]
146                 menu = hildonize.hildonize_menu(
147                         self._window,
148                         self._widgetTree.get_widget("dialpad_menubar"),
149                         replacementButtons
150                 )
151
152                 self._window.connect("key-press-event", self._on_key_press)
153                 self._window.connect("window-state-event", self._on_window_state_change)
154                 if not hildonize.IS_HILDON_SUPPORTED:
155                         _moduleLogger.warning("No hildonization support")
156
157                 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
158
159                 self._window.connect("destroy", self._on_close)
160                 self._window.set_default_size(800, 300)
161                 self._window.show_all()
162
163                 self._loginSink = gtk_toolbox.threaded_stage(
164                         gtk_toolbox.comap(
165                                 self._attempt_login,
166                                 gtk_toolbox.null_sink(),
167                         )
168                 )
169
170                 backgroundSetup = threading.Thread(target=self._idle_setup)
171                 backgroundSetup.setDaemon(True)
172                 backgroundSetup.start()
173
174         def _idle_setup(self):
175                 """
176                 If something can be done after the UI loads, push it here so it's not blocking the UI
177                 """
178                 # Barebones UI handlers
179                 try:
180                         import null_backend
181                         import null_views
182
183                         self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
184                         with gtk_toolbox.gtk_lock():
185                                 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
186                                 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
187                                 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
188                                 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
189                                 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
190
191                                 self._dialpads[self._selectedBackendId].enable()
192                                 self._accountViews[self._selectedBackendId].enable()
193                                 self._recentViews[self._selectedBackendId].enable()
194                                 self._messagesViews[self._selectedBackendId].enable()
195                                 self._contactsViews[self._selectedBackendId].enable()
196                 except Exception, e:
197                         with gtk_toolbox.gtk_lock():
198                                 self._errorDisplay.push_exception()
199
200                 # Setup maemo specifics
201                 try:
202                         try:
203                                 import osso
204                         except (ImportError, OSError):
205                                 osso = None
206                         self._osso = None
207                         if osso is not None:
208                                 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
209                                 device = osso.DeviceState(self._osso)
210                                 device.set_device_state_callback(self._on_device_state_change, 0)
211                         else:
212                                 _moduleLogger.warning("No device state support")
213
214                         try:
215                                 import alarm_handler
216                                 self._alarmHandler = alarm_handler.AlarmHandler()
217                         except (ImportError, OSError):
218                                 alarm_handler = None
219                         except Exception:
220                                 with gtk_toolbox.gtk_lock():
221                                         self._errorDisplay.push_exception()
222                                 alarm_handler = None
223                                 _moduleLogger.warning("No notification support")
224                         if hildonize.IS_HILDON_SUPPORTED:
225                                 try:
226                                         import led_handler
227                                         self._ledHandler = led_handler.LedHandler()
228                                 except Exception, e:
229                                         _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
230                                         self._ledHandler = None
231                         else:
232                                 self._ledHandler = None
233
234                         try:
235                                 import conic
236                         except (ImportError, OSError):
237                                 conic = None
238                         self._connection = None
239                         if conic is not None:
240                                 self._connection = conic.Connection()
241                                 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
242                                 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
243                         else:
244                                 _moduleLogger.warning("No connection support")
245                 except Exception, e:
246                         with gtk_toolbox.gtk_lock():
247                                 self._errorDisplay.push_exception()
248
249                 # Setup costly backends
250                 try:
251                         import gv_backend
252                         import file_backend
253                         import gv_views
254
255                         try:
256                                 os.makedirs(constants._data_path_)
257                         except OSError, e:
258                                 if e.errno != 17:
259                                         raise
260                         gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
261
262                         self._phoneBackends.update({
263                                 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
264                         })
265                         with gtk_toolbox.gtk_lock():
266                                 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
267                                 self._dialpads.update({
268                                         self.GV_BACKEND: unifiedDialpad,
269                                 })
270                                 self._accountViews.update({
271                                         self.GV_BACKEND: gv_views.AccountInfo(
272                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
273                                         ),
274                                 })
275                                 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
276                                 self._recentViews.update({
277                                         self.GV_BACKEND: gv_views.RecentCallsView(
278                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
279                                         ),
280                                 })
281                                 self._messagesViews.update({
282                                         self.GV_BACKEND: gv_views.MessagesView(
283                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
284                                         ),
285                                 })
286                                 self._contactsViews.update({
287                                         self.GV_BACKEND: gv_views.ContactsView(
288                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
289                                         ),
290                                 })
291
292                         fsContactsPath = os.path.join(constants._data_path_, "contacts")
293                         fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
294
295                         self._dialpads[self.GV_BACKEND].number_selected = self._select_action
296                         self._recentViews[self.GV_BACKEND].number_selected = self._select_action
297                         self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
298                         self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
299
300                         addressBooks = [
301                                 self._phoneBackends[self.GV_BACKEND],
302                                 fileBackend,
303                         ]
304                         mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
305                         self._contactsViews[self.GV_BACKEND].append(mergedBook)
306                         self._contactsViews[self.GV_BACKEND].extend(addressBooks)
307                         self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
308
309                         callbackMapping = {
310                                 "on_paste": self._on_paste,
311                                 "on_refresh": self._on_menu_refresh,
312                                 "on_clearcookies_clicked": self._on_clearcookies_clicked,
313                                 "on_about_activate": self._on_about_activate,
314                         }
315                         if hildonize.GTK_MENU_USED:
316                                 self._widgetTree.signal_autoconnect(callbackMapping)
317                         self._notebook.connect("switch-page", self._on_notebook_switch_page)
318                         self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
319
320                         with gtk_toolbox.gtk_lock():
321                                 self._originalCurrentLabels = [
322                                         self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
323                                         for pageIndex in xrange(self._notebook.get_n_pages())
324                                 ]
325                                 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
326                                 self._notebookTapHandler.enable()
327                         self._notebookTapHandler.on_tap = self._reset_tab_refresh
328                         self._notebookTapHandler.on_hold = self._on_tab_refresh
329                         self._notebookTapHandler.on_holding = self._set_tab_refresh
330                         self._notebookTapHandler.on_cancel = self._reset_tab_refresh
331
332                         self._initDone = True
333
334                         config = ConfigParser.SafeConfigParser()
335                         config.read(constants._user_settings_)
336                         with gtk_toolbox.gtk_lock():
337                                 self.load_settings(config)
338                 except Exception, e:
339                         with gtk_toolbox.gtk_lock():
340                                 self._errorDisplay.push_exception()
341                 finally:
342                         self._spawn_attempt_login(2)
343
344         def _spawn_attempt_login(self, *args):
345                 self._loginSink.send(args)
346
347         def _attempt_login(self, numOfAttempts = 10, force = False):
348                 """
349                 @note This must be run outside of the UI lock
350                 """
351                 try:
352                         assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
353                         assert self._initDone, "Attempting login before app is fully loaded"
354
355                         serviceId = self.NULL_BACKEND
356                         loggedIn = False
357                         if not force:
358                                 with gtk_toolbox.gtk_lock():
359                                         banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
360                                 try:
361                                         self.refresh_session()
362                                         serviceId = self._defaultBackendId
363                                         loggedIn = True
364                                 except Exception, e:
365                                         _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
366                                 finally:
367                                         with gtk_toolbox.gtk_lock():
368                                                 hildonize.show_busy_banner_end(banner)
369
370                         if not loggedIn:
371                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
372
373                         with gtk_toolbox.gtk_lock():
374                                 self._change_loggedin_status(serviceId)
375                                 if loggedIn:
376                                         hildonize.show_information_banner(self._window, "Logged In")
377                                 else:
378                                         hildonize.show_information_banner(self._window, "Login Failed")
379                 except Exception, e:
380                         with gtk_toolbox.gtk_lock():
381                                 self._errorDisplay.push_exception()
382
383         def refresh_session(self):
384                 """
385                 @note Thread agnostic
386                 """
387                 assert self._initDone, "Attempting login before app is fully loaded"
388
389                 loggedIn = False
390                 if not loggedIn:
391                         loggedIn = self._login_by_cookie()
392                 if not loggedIn:
393                         loggedIn = self._login_by_settings()
394
395                 if not loggedIn:
396                         raise RuntimeError("Login Failed")
397
398         def _login_by_cookie(self):
399                 """
400                 @note Thread agnostic
401                 """
402                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
403                 if loggedIn:
404                         _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
405                 return loggedIn
406
407         def _login_by_settings(self):
408                 """
409                 @note Thread agnostic
410                 """
411                 username, password = self._credentials
412                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
413                 if loggedIn:
414                         self._credentials = username, password
415                         _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
416                 return loggedIn
417
418         def _login_by_user(self, numOfAttempts):
419                 """
420                 @note This must be run outside of the UI lock
421                 """
422                 loggedIn, (username, password) = False, self._credentials
423                 tmpServiceId = self.GV_BACKEND
424                 for attemptCount in xrange(numOfAttempts):
425                         if loggedIn:
426                                 break
427                         with gtk_toolbox.gtk_lock():
428                                 credentials = self._credentialsDialog.request_credentials(
429                                         defaultCredentials = self._credentials
430                                 )
431                                 if not self._phoneBackends[tmpServiceId].get_callback_number():
432                                         # subtle reminder to the users to configure things
433                                         self._notebook.set_current_page(self.ACCOUNT_TAB)
434                                 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
435                         try:
436                                 username, password = credentials
437                                 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
438                         finally:
439                                 with gtk_toolbox.gtk_lock():
440                                         hildonize.show_busy_banner_end(banner)
441
442                 if loggedIn:
443                         serviceId = tmpServiceId
444                         self._credentials = username, password
445                         _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
446                 else:
447                         serviceId = self.NULL_BACKEND
448                         self._notebook.set_current_page(self.ACCOUNT_TAB)
449
450                 return loggedIn, serviceId
451
452         def _select_action(self, action, number, message):
453                 self.refresh_session()
454                 if action == "select":
455                         self._dialpads[self._selectedBackendId].set_number(number)
456                         self._notebook.set_current_page(self.KEYPAD_TAB)
457                 elif action == "dial":
458                         self._on_dial_clicked(number)
459                 elif action == "sms":
460                         self._on_sms_clicked(number, message)
461                 else:
462                         assert False, "Unknown action: %s" % action
463
464         def _change_loggedin_status(self, newStatus):
465                 oldStatus = self._selectedBackendId
466                 if oldStatus == newStatus:
467                         return
468
469                 self._dialpads[oldStatus].disable()
470                 self._accountViews[oldStatus].disable()
471                 self._recentViews[oldStatus].disable()
472                 self._messagesViews[oldStatus].disable()
473                 self._contactsViews[oldStatus].disable()
474
475                 self._dialpads[newStatus].enable()
476                 self._accountViews[newStatus].enable()
477                 self._recentViews[newStatus].enable()
478                 self._messagesViews[newStatus].enable()
479                 self._contactsViews[newStatus].enable()
480
481                 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
482                         self._phoneBackends[self._selectedBackendId].set_sane_callback()
483
484                 self._selectedBackendId = newStatus
485
486                 self._accountViews[self._selectedBackendId].update()
487                 self._refresh_active_tab()
488
489         def load_settings(self, config):
490                 """
491                 @note UI Thread
492                 """
493                 try:
494                         self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
495                         blobs = (
496                                 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
497                                 for i in xrange(len(self._credentials))
498                         )
499                         creds = (
500                                 base64.b64decode(blob)
501                                 for blob in blobs
502                         )
503                         self._credentials = tuple(creds)
504
505                         if self._alarmHandler is not None:
506                                 self._alarmHandler.load_settings(config, "alarm")
507                 except ConfigParser.NoOptionError, e:
508                         _moduleLogger.exception(
509                                 "Settings file %s is missing section %s" % (
510                                         constants._user_settings_,
511                                         e.section,
512                                 ),
513                         )
514                 except ConfigParser.NoSectionError, e:
515                         _moduleLogger.exception(
516                                 "Settings file %s is missing section %s" % (
517                                         constants._user_settings_,
518                                         e.section,
519                                 ),
520                         )
521
522                 for backendId, view in itertools.chain(
523                         self._dialpads.iteritems(),
524                         self._accountViews.iteritems(),
525                         self._messagesViews.iteritems(),
526                         self._recentViews.iteritems(),
527                         self._contactsViews.iteritems(),
528                 ):
529                         sectionName = "%s - %s" % (backendId, view.name())
530                         try:
531                                 view.load_settings(config, sectionName)
532                         except ConfigParser.NoOptionError, e:
533                                 _moduleLogger.exception(
534                                         "Settings file %s is missing section %s" % (
535                                                 constants._user_settings_,
536                                                 e.section,
537                                         ),
538                                 )
539                         except ConfigParser.NoSectionError, e:
540                                 _moduleLogger.exception(
541                                         "Settings file %s is missing section %s" % (
542                                                 constants._user_settings_,
543                                                 e.section,
544                                         ),
545                                 )
546
547                 try:
548                         previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
549                         if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
550                                 hildonize.window_to_landscape(self._window)
551                         elif previousOrientation == gtk.ORIENTATION_VERTICAL:
552                                 hildonize.window_to_portrait(self._window)
553                 except ConfigParser.NoOptionError, e:
554                         _moduleLogger.exception(
555                                 "Settings file %s is missing section %s" % (
556                                         constants._user_settings_,
557                                         e.section,
558                                 ),
559                         )
560                 except ConfigParser.NoSectionError, e:
561                         _moduleLogger.exception(
562                                 "Settings file %s is missing section %s" % (
563                                         constants._user_settings_,
564                                         e.section,
565                                 ),
566                         )
567
568         def save_settings(self, config):
569                 """
570                 @note Thread Agnostic
571                 """
572                 config.add_section(constants.__pretty_app_name__)
573                 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
574                 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
575                 for i, value in enumerate(self._credentials):
576                         blob = base64.b64encode(value)
577                         config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
578                 config.add_section("alarm")
579                 if self._alarmHandler is not None:
580                         self._alarmHandler.save_settings(config, "alarm")
581
582                 for backendId, view in itertools.chain(
583                         self._dialpads.iteritems(),
584                         self._accountViews.iteritems(),
585                         self._messagesViews.iteritems(),
586                         self._recentViews.iteritems(),
587                         self._contactsViews.iteritems(),
588                 ):
589                         sectionName = "%s - %s" % (backendId, view.name())
590                         config.add_section(sectionName)
591                         view.save_settings(config, sectionName)
592
593         def _save_settings(self):
594                 """
595                 @note Thread Agnostic
596                 """
597                 config = ConfigParser.SafeConfigParser()
598                 self.save_settings(config)
599                 with open(constants._user_settings_, "wb") as configFile:
600                         config.write(configFile)
601
602         def _refresh_active_tab(self):
603                 pageIndex = self._notebook.get_current_page()
604                 if pageIndex == self.CONTACTS_TAB:
605                         self._contactsViews[self._selectedBackendId].update(force=True)
606                 elif pageIndex == self.RECENT_TAB:
607                         self._recentViews[self._selectedBackendId].update(force=True)
608                 elif pageIndex == self.MESSAGES_TAB:
609                         self._messagesViews[self._selectedBackendId].update(force=True)
610
611                 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
612                         if self._ledHandler is not None:
613                                 self._ledHandler.off()
614
615         def _on_close(self, *args, **kwds):
616                 try:
617                         if self._osso is not None:
618                                 self._osso.close()
619
620                         if self._initDone:
621                                 self._save_settings()
622                 finally:
623                         gtk.main_quit()
624
625         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
626                 """
627                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
628                 For system_inactivity, we have no background tasks to pause
629
630                 @note Hildon specific
631                 """
632                 try:
633                         if memory_low:
634                                 for backendId in self.BACKENDS:
635                                         self._phoneBackends[backendId].clear_caches()
636                                 self._contactsViews[self._selectedBackendId].clear_caches()
637                                 gc.collect()
638
639                         if save_unsaved_data or shutdown:
640                                 self._save_settings()
641                 except Exception, e:
642                         self._errorDisplay.push_exception()
643
644         def _on_connection_change(self, connection, event, magicIdentifier):
645                 """
646                 @note Hildon specific
647                 """
648                 try:
649                         import conic
650
651                         status = event.get_status()
652                         error = event.get_error()
653                         iap_id = event.get_iap_id()
654                         bearer = event.get_bearer_type()
655
656                         if status == conic.STATUS_CONNECTED:
657                                 if self._initDone:
658                                         self._spawn_attempt_login(2)
659                         elif status == conic.STATUS_DISCONNECTED:
660                                 if self._initDone:
661                                         self._defaultBackendId = self._selectedBackendId
662                                         self._change_loggedin_status(self.NULL_BACKEND)
663                 except Exception, e:
664                         self._errorDisplay.push_exception()
665
666         def _on_window_state_change(self, widget, event, *args):
667                 """
668                 @note Hildon specific
669                 """
670                 try:
671                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
672                                 self._isFullScreen = True
673                         else:
674                                 self._isFullScreen = False
675                 except Exception, e:
676                         self._errorDisplay.push_exception()
677
678         def _on_key_press(self, widget, event, *args):
679                 """
680                 @note Hildon specific
681                 """
682                 try:
683                         if (
684                                 event.keyval == gtk.keysyms.F6 or
685                                 event.keyval == gtk.keysyms.Return and event.get_state() & gtk.gdk.CONTROL_MASK
686                         ):
687                                 if self._isFullScreen:
688                                         self._window.unfullscreen()
689                                 else:
690                                         self._window.fullscreen()
691                 except Exception, e:
692                         self._errorDisplay.push_exception()
693
694         def _on_clearcookies_clicked(self, *args):
695                 try:
696                         self._phoneBackends[self._selectedBackendId].logout()
697                         self._accountViews[self._selectedBackendId].clear()
698                         self._recentViews[self._selectedBackendId].clear()
699                         self._messagesViews[self._selectedBackendId].clear()
700                         self._contactsViews[self._selectedBackendId].clear()
701                         self._change_loggedin_status(self.NULL_BACKEND)
702
703                         self._spawn_attempt_login(2, True)
704                 except Exception, e:
705                         self._errorDisplay.push_exception()
706
707         def _on_notebook_switch_page(self, notebook, page, pageIndex):
708                 try:
709                         self._reset_tab_refresh()
710
711                         didRecentUpdate = False
712                         didMessagesUpdate = False
713
714                         if pageIndex == self.RECENT_TAB:
715                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
716                         elif pageIndex == self.MESSAGES_TAB:
717                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
718                         elif pageIndex == self.CONTACTS_TAB:
719                                 self._contactsViews[self._selectedBackendId].update()
720                         elif pageIndex == self.ACCOUNT_TAB:
721                                 self._accountViews[self._selectedBackendId].update()
722
723                         if didRecentUpdate or didMessagesUpdate:
724                                 if self._ledHandler is not None:
725                                         self._ledHandler.off()
726                 except Exception, e:
727                         self._errorDisplay.push_exception()
728
729         def _set_tab_refresh(self, *args):
730                 try:
731                         pageIndex = self._notebook.get_current_page()
732                         child = self._notebook.get_nth_page(pageIndex)
733                         self._notebook.get_tab_label(child).set_text("Refresh?")
734                 except Exception, e:
735                         self._errorDisplay.push_exception()
736                 return False
737
738         def _reset_tab_refresh(self, *args):
739                 try:
740                         pageIndex = self._notebook.get_current_page()
741                         child = self._notebook.get_nth_page(pageIndex)
742                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
743                 except Exception, e:
744                         self._errorDisplay.push_exception()
745                 return False
746
747         def _on_tab_refresh(self, *args):
748                 try:
749                         self._refresh_active_tab()
750                         self._reset_tab_refresh()
751                 except Exception, e:
752                         self._errorDisplay.push_exception()
753                 return False
754
755         def _on_sms_clicked(self, number, message):
756                 try:
757                         assert number, "No number specified"
758                         assert message, "Empty message"
759                         try:
760                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
761                         except Exception, e:
762                                 loggedIn = False
763                                 self._errorDisplay.push_exception()
764                                 return
765
766                         if not loggedIn:
767                                 self._errorDisplay.push_message(
768                                         "Backend link with GoogleVoice is not working, please try again"
769                                 )
770                                 return
771
772                         dialed = False
773                         try:
774                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
775                                 hildonize.show_information_banner(self._window, "Sending to %s" % number)
776                                 dialed = True
777                         except Exception, e:
778                                 self._errorDisplay.push_exception()
779
780                         if dialed:
781                                 self._dialpads[self._selectedBackendId].clear()
782                 except Exception, e:
783                         self._errorDisplay.push_exception()
784
785         def _on_dial_clicked(self, number):
786                 try:
787                         assert number, "No number to call"
788                         try:
789                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
790                         except Exception, e:
791                                 loggedIn = False
792                                 self._errorDisplay.push_exception()
793                                 return
794
795                         if not loggedIn:
796                                 self._errorDisplay.push_message(
797                                         "Backend link with GoogleVoice is not working, please try again"
798                                 )
799                                 return
800
801                         dialed = False
802                         try:
803                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
804                                 self._phoneBackends[self._selectedBackendId].dial(number)
805                                 hildonize.show_information_banner(self._window, "Calling %s" % number)
806                                 dialed = True
807                         except Exception, e:
808                                 self._errorDisplay.push_exception()
809
810                         if dialed:
811                                 self._dialpads[self._selectedBackendId].clear()
812                 except Exception, e:
813                         self._errorDisplay.push_exception()
814
815         def _on_menu_refresh(self, *args):
816                 try:
817                         self._refresh_active_tab()
818                 except Exception, e:
819                         self._errorDisplay.push_exception()
820
821         def _on_paste(self, *args):
822                 try:
823                         contents = self._clipboard.wait_for_text()
824                         if contents is not None:
825                                 self._dialpads[self._selectedBackendId].set_number(contents)
826                 except Exception, e:
827                         self._errorDisplay.push_exception()
828
829         def _on_about_activate(self, *args):
830                 try:
831                         dlg = gtk.AboutDialog()
832                         dlg.set_name(constants.__pretty_app_name__)
833                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
834                         dlg.set_copyright("Copyright 2008 - LGPL")
835                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account.  This application is not affiliated with Google in any way")
836                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
837                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
838                         dlg.run()
839                         dlg.destroy()
840                 except Exception, e:
841                         self._errorDisplay.push_exception()
842
843
844 def run_doctest():
845         import doctest
846
847         failureCount, testCount = doctest.testmod()
848         if not failureCount:
849                 print "Tests Successful"
850                 sys.exit(0)
851         else:
852                 sys.exit(1)
853
854
855 def run_dialpad():
856         _lock_file = os.path.join(constants._data_path_, ".lock")
857         #with gtk_toolbox.flock(_lock_file, 0):
858         gtk.gdk.threads_init()
859
860         if hildonize.IS_HILDON_SUPPORTED:
861                 gtk.set_application_name(constants.__pretty_app_name__)
862         handle = Dialcentral()
863         gtk.main()
864
865
866 class DummyOptions(object):
867
868         def __init__(self):
869                 self.test = False
870
871
872 if __name__ == "__main__":
873         logging.basicConfig(level=logging.DEBUG)
874         try:
875                 if len(sys.argv) > 1:
876                         try:
877                                 import optparse
878                         except ImportError:
879                                 optparse = None
880
881                         if optparse is not None:
882                                 parser = optparse.OptionParser()
883                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
884                                 (commandOptions, commandArgs) = parser.parse_args()
885                 else:
886                         commandOptions = DummyOptions()
887                         commandArgs = []
888
889                 if commandOptions.test:
890                         run_doctest()
891                 else:
892                         run_dialpad()
893         finally:
894                 logging.shutdown()