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