Fixing refresh bugs
[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                 hildonize.hildonize_combo_entry(self._widgetTree.get_widget("callbackcombo").get_child())
115
116                 for scrollingWidget in (
117                         'recent_scrolledwindow',
118                         'message_scrolledwindow',
119                         'contacts_scrolledwindow',
120                         "phoneSelectionMessages_scrolledwindow",
121                         "smsMessages_scrolledwindow",
122                 ):
123                         hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
124                 for scrollingWidget in (
125                         "phonetypes_scrolledwindow",
126                         "smsMessage_scrolledEntry",
127                 ):
128                         hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
129
130                 replacementButtons = [gtk.Button("Test")]
131                 menu = hildonize.hildonize_menu(
132                         self._window,
133                         self._widgetTree.get_widget("dialpad_menubar"),
134                         replacementButtons
135                 )
136
137                 if hildonize.IS_HILDON_SUPPORTED:
138                         self._window.connect("key-press-event", self._on_key_press)
139                         self._window.connect("window-state-event", self._on_window_state_change)
140                 else:
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 event.keyval == gtk.keysyms.F6:
658                                 if self._isFullScreen:
659                                         self._window.unfullscreen()
660                                 else:
661                                         self._window.fullscreen()
662                 except Exception, e:
663                         self._errorDisplay.push_exception()
664
665         def _on_clearcookies_clicked(self, *args):
666                 try:
667                         self._phoneBackends[self._selectedBackendId].logout()
668                         self._accountViews[self._selectedBackendId].clear()
669                         self._recentViews[self._selectedBackendId].clear()
670                         self._messagesViews[self._selectedBackendId].clear()
671                         self._contactsViews[self._selectedBackendId].clear()
672                         self._change_loggedin_status(self.NULL_BACKEND)
673
674                         self._spawn_attempt_login(2, True)
675                 except Exception, e:
676                         self._errorDisplay.push_exception()
677
678         def _on_notebook_switch_page(self, notebook, page, pageIndex):
679                 try:
680                         self._reset_tab_refresh()
681
682                         didRecentUpdate = False
683                         didMessagesUpdate = False
684
685                         if pageIndex == self.RECENT_TAB:
686                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
687                         elif pageIndex == self.MESSAGES_TAB:
688                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
689                         elif pageIndex == self.CONTACTS_TAB:
690                                 self._contactsViews[self._selectedBackendId].update()
691                         elif pageIndex == self.ACCOUNT_TAB:
692                                 self._accountViews[self._selectedBackendId].update()
693
694                         if didRecentUpdate or didMessagesUpdate:
695                                 if self._ledHandler is not None:
696                                         self._ledHandler.off()
697                 except Exception, e:
698                         self._errorDisplay.push_exception()
699
700         def _set_tab_refresh(self, *args):
701                 try:
702                         pageIndex = self._notebook.get_current_page()
703                         child = self._notebook.get_nth_page(pageIndex)
704                         self._notebook.get_tab_label(child).set_text("Refresh?")
705                 except Exception, e:
706                         self._errorDisplay.push_exception()
707                 return False
708
709         def _reset_tab_refresh(self, *args):
710                 try:
711                         pageIndex = self._notebook.get_current_page()
712                         child = self._notebook.get_nth_page(pageIndex)
713                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
714                 except Exception, e:
715                         self._errorDisplay.push_exception()
716                 return False
717
718         def _on_tab_refresh(self, *args):
719                 try:
720                         self._refresh_active_tab()
721                         self._reset_tab_refresh()
722                 except Exception, e:
723                         self._errorDisplay.push_exception()
724                 return False
725
726         def _on_sms_clicked(self, number, message):
727                 try:
728                         assert number, "No number specified"
729                         assert message, "Empty message"
730                         try:
731                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
732                         except Exception, e:
733                                 loggedIn = False
734                                 self._errorDisplay.push_exception()
735                                 return
736
737                         if not loggedIn:
738                                 self._errorDisplay.push_message(
739                                         "Backend link with GoogleVoice is not working, please try again"
740                                 )
741                                 return
742
743                         dialed = False
744                         try:
745                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
746                                 hildonize.show_information_banner(self._window, "Sending to %s" % number)
747                                 dialed = True
748                         except Exception, e:
749                                 self._errorDisplay.push_exception()
750
751                         if dialed:
752                                 self._dialpads[self._selectedBackendId].clear()
753                 except Exception, e:
754                         self._errorDisplay.push_exception()
755
756         def _on_dial_clicked(self, number):
757                 try:
758                         assert number, "No number to call"
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                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
775                                 self._phoneBackends[self._selectedBackendId].dial(number)
776                                 hildonize.show_information_banner(self._window, "Calling %s" % number)
777                                 dialed = True
778                         except Exception, e:
779                                 self._errorDisplay.push_exception()
780
781                         if dialed:
782                                 self._dialpads[self._selectedBackendId].clear()
783                 except Exception, e:
784                         self._errorDisplay.push_exception()
785
786         def _on_menu_refresh(self, *args):
787                 try:
788                         self._refresh_active_tab()
789                 except Exception, e:
790                         self._errorDisplay.push_exception()
791
792         def _on_paste(self, *args):
793                 try:
794                         contents = self._clipboard.wait_for_text()
795                         if contents is not None:
796                                 self._dialpads[self._selectedBackendId].set_number(contents)
797                 except Exception, e:
798                         self._errorDisplay.push_exception()
799
800         def _on_about_activate(self, *args):
801                 try:
802                         dlg = gtk.AboutDialog()
803                         dlg.set_name(constants.__pretty_app_name__)
804                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
805                         dlg.set_copyright("Copyright 2008 - LGPL")
806                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account.  This application is not affiliated with Google in any way")
807                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
808                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
809                         dlg.run()
810                         dlg.destroy()
811                 except Exception, e:
812                         self._errorDisplay.push_exception()
813
814
815 def run_doctest():
816         import doctest
817
818         failureCount, testCount = doctest.testmod()
819         if not failureCount:
820                 print "Tests Successful"
821                 sys.exit(0)
822         else:
823                 sys.exit(1)
824
825
826 def run_dialpad():
827         _lock_file = os.path.join(constants._data_path_, ".lock")
828         #with gtk_toolbox.flock(_lock_file, 0):
829         gtk.gdk.threads_init()
830
831         if hildonize.IS_HILDON_SUPPORTED:
832                 gtk.set_application_name(constants.__pretty_app_name__)
833         handle = Dialcentral()
834         gtk.main()
835
836
837 class DummyOptions(object):
838
839         def __init__(self):
840                 self.test = False
841
842
843 if __name__ == "__main__":
844         logging.basicConfig(level=logging.DEBUG)
845         try:
846                 if len(sys.argv) > 1:
847                         try:
848                                 import optparse
849                         except ImportError:
850                                 optparse = None
851
852                         if optparse is not None:
853                                 parser = optparse.OptionParser()
854                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
855                                 (commandOptions, commandArgs) = parser.parse_args()
856                 else:
857                         commandOptions = DummyOptions()
858                         commandArgs = []
859
860                 if commandOptions.test:
861                         run_doctest()
862                 else:
863                         run_dialpad()
864         finally:
865                 logging.shutdown()