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