Moving some settings around to make life better for loading settigns
[gc-dialer] / src / dc_glade.py
1 #!/usr/bin/python2.5
2
3 """
4 DialCentral - Front end for Google's Grand Central 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                         "phoneSelectionMessage_scrolledwindow",
121                         "phonetypes_scrolledwindow",
122                         "smsMessage_scrolledwindow",
123                         "smsMessage_scrolledEntry",
124                 ):
125                         hildonize.set_thumb_scrollbar(self._widgetTree.get_widget(scrollingWidget))
126
127                 hildonize.hildonize_menu(self._window, self._widgetTree.get_widget("dialpad_menubar"))
128
129                 if hildonize.IS_HILDON:
130                         self._window.connect("key-press-event", self._on_key_press)
131                         self._window.connect("window-state-event", self._on_window_state_change)
132                 else:
133                         logging.warning("No hildonization support")
134
135
136                 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
137
138                 callbackMapping = {
139                         "on_dialpad_quit": self._on_close,
140                 }
141                 self._widgetTree.signal_autoconnect(callbackMapping)
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:
209                                 import led_handler
210                                 self._ledHandler = led_handler.LedHandler()
211
212                         try:
213                                 import conic
214                         except (ImportError, OSError):
215                                 conic = None
216                         self._connection = None
217                         if conic is not None:
218                                 self._connection = conic.Connection()
219                                 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
220                                 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
221                         else:
222                                 logging.warning("No connection support")
223                 except Exception, e:
224                         with gtk_toolbox.gtk_lock():
225                                 self._errorDisplay.push_exception()
226
227                 # Setup costly backends
228                 try:
229                         import gv_backend
230                         import file_backend
231                         import gv_views
232
233                         try:
234                                 os.makedirs(constants._data_path_)
235                         except OSError, e:
236                                 if e.errno != 17:
237                                         raise
238                         gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
239
240                         self._phoneBackends.update({
241                                 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
242                         })
243                         with gtk_toolbox.gtk_lock():
244                                 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
245                                 self._dialpads.update({
246                                         self.GV_BACKEND: unifiedDialpad,
247                                 })
248                                 self._accountViews.update({
249                                         self.GV_BACKEND: gv_views.AccountInfo(
250                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
251                                         ),
252                                 })
253                                 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
254                                 self._recentViews.update({
255                                         self.GV_BACKEND: gv_views.RecentCallsView(
256                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
257                                         ),
258                                 })
259                                 self._messagesViews.update({
260                                         self.GV_BACKEND: gv_views.MessagesView(
261                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
262                                         ),
263                                 })
264                                 self._contactsViews.update({
265                                         self.GV_BACKEND: gv_views.ContactsView(
266                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
267                                         ),
268                                 })
269
270                         fsContactsPath = os.path.join(constants._data_path_, "contacts")
271                         fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
272
273                         self._dialpads[self.GV_BACKEND].number_selected = self._select_action
274                         self._recentViews[self.GV_BACKEND].number_selected = self._select_action
275                         self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
276                         self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
277
278                         addressBooks = [
279                                 self._phoneBackends[self.GV_BACKEND],
280                                 fileBackend,
281                         ]
282                         mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
283                         self._contactsViews[self.GV_BACKEND].append(mergedBook)
284                         self._contactsViews[self.GV_BACKEND].extend(addressBooks)
285                         self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
286
287                         callbackMapping = {
288                                 "on_paste": self._on_paste,
289                                 "on_refresh": self._on_menu_refresh,
290                                 "on_rotate": self._on_menu_rotate,
291                                 "on_clearcookies_clicked": self._on_clearcookies_clicked,
292                                 "on_notebook_switch_page": self._on_notebook_switch_page,
293                                 "on_about_activate": self._on_about_activate,
294                         }
295                         self._widgetTree.signal_autoconnect(callbackMapping)
296
297                         with gtk_toolbox.gtk_lock():
298                                 self._originalCurrentLabels = [
299                                         self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
300                                         for pageIndex in xrange(self._notebook.get_n_pages())
301                                 ]
302                                 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
303                                 self._notebookTapHandler.enable()
304                         self._notebookTapHandler.on_tap = self._reset_tab_refresh
305                         self._notebookTapHandler.on_hold = self._on_tab_refresh
306                         self._notebookTapHandler.on_holding = self._set_tab_refresh
307                         self._notebookTapHandler.on_cancel = self._reset_tab_refresh
308
309                         self._initDone = True
310
311                         config = ConfigParser.SafeConfigParser()
312                         config.read(constants._user_settings_)
313                         with gtk_toolbox.gtk_lock():
314                                 self.load_settings(config)
315                 except Exception, e:
316                         with gtk_toolbox.gtk_lock():
317                                 self._errorDisplay.push_exception()
318                 finally:
319                         self._spawn_attempt_login(2)
320
321         def attempt_login(self, numOfAttempts = 10, force = False):
322                 """
323                 @todo Handle user notification better like attempting to login and failed login
324
325                 @note This must be run outside of the UI lock
326                 """
327                 try:
328                         assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
329                         assert self._initDone, "Attempting login before app is fully loaded"
330
331                         serviceId = self.NULL_BACKEND
332                         loggedIn = False
333                         if not force:
334                                 try:
335                                         self.refresh_session()
336                                         serviceId = self._defaultBackendId
337                                         loggedIn = True
338                                 except Exception, e:
339                                         logging.exception('Session refresh failed with the following message "%s"' % e.message)
340
341                         if not loggedIn:
342                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
343
344                         with gtk_toolbox.gtk_lock():
345                                 self._change_loggedin_status(serviceId)
346                 except Exception, e:
347                         with gtk_toolbox.gtk_lock():
348                                 self._errorDisplay.push_exception()
349
350         def _spawn_attempt_login(self, *args):
351                 self._loginSink.send(args)
352
353         def refresh_session(self):
354                 """
355                 @note Thread agnostic
356                 """
357                 assert self._initDone, "Attempting login before app is fully loaded"
358
359                 loggedIn = False
360                 if not loggedIn:
361                         loggedIn = self._login_by_cookie()
362                 if not loggedIn:
363                         loggedIn = self._login_by_settings()
364
365                 if not loggedIn:
366                         raise RuntimeError("Login Failed")
367
368         def _login_by_cookie(self):
369                 """
370                 @note Thread agnostic
371                 """
372                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
373                 if loggedIn:
374                         logging.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
375                 return loggedIn
376
377         def _login_by_settings(self):
378                 """
379                 @note Thread agnostic
380                 """
381                 username, password = self._credentials
382                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
383                 if loggedIn:
384                         self._credentials = username, password
385                         logging.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
386                 return loggedIn
387
388         def _login_by_user(self, numOfAttempts):
389                 """
390                 @note This must be run outside of the UI lock
391                 """
392                 loggedIn, (username, password) = False, self._credentials
393                 tmpServiceId = self.GV_BACKEND
394                 for attemptCount in xrange(numOfAttempts):
395                         if loggedIn:
396                                 break
397                         with gtk_toolbox.gtk_lock():
398                                 credentials = self._credentialsDialog.request_credentials(
399                                         defaultCredentials = self._credentials
400                                 )
401                         username, password = credentials
402                         loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
403
404                 if loggedIn:
405                         serviceId = tmpServiceId
406                         self._credentials = username, password
407                         logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
408                 else:
409                         serviceId = self.NULL_BACKEND
410
411                 return loggedIn, serviceId
412
413         def _select_action(self, action, number, message):
414                 self.refresh_session()
415                 if action == "select":
416                         self._dialpads[self._selectedBackendId].set_number(number)
417                         self._notebook.set_current_page(self.KEYPAD_TAB)
418                 elif action == "dial":
419                         self._on_dial_clicked(number)
420                 elif action == "sms":
421                         self._on_sms_clicked(number, message)
422                 else:
423                         assert False, "Unknown action: %s" % action
424
425         def _change_loggedin_status(self, newStatus):
426                 oldStatus = self._selectedBackendId
427                 if oldStatus == newStatus:
428                         return
429
430                 self._dialpads[oldStatus].disable()
431                 self._accountViews[oldStatus].disable()
432                 self._recentViews[oldStatus].disable()
433                 self._messagesViews[oldStatus].disable()
434                 self._contactsViews[oldStatus].disable()
435
436                 self._dialpads[newStatus].enable()
437                 self._accountViews[newStatus].enable()
438                 self._recentViews[newStatus].enable()
439                 self._messagesViews[newStatus].enable()
440                 self._contactsViews[newStatus].enable()
441
442                 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
443                         self._phoneBackends[self._selectedBackendId].set_sane_callback()
444                 self._accountViews[self._selectedBackendId].update()
445
446                 self._selectedBackendId = newStatus
447
448         def load_settings(self, config):
449                 """
450                 @note UI Thread
451                 """
452                 try:
453                         self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
454                         blobs = (
455                                 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
456                                 for i in xrange(len(self._credentials))
457                         )
458                         creds = (
459                                 base64.b64decode(blob)
460                                 for blob in blobs
461                         )
462                         self._credentials = tuple(creds)
463
464                         if self._alarmHandler is not None:
465                                 self._alarmHandler.load_settings(config, "alarm")
466                 except ConfigParser.NoOptionError, e:
467                         logging.exception(
468                                 "Settings file %s is missing section %s" % (
469                                         constants._user_settings_,
470                                         e.section,
471                                 ),
472                         )
473                 except ConfigParser.NoSectionError, e:
474                         logging.exception(
475                                 "Settings file %s is missing section %s" % (
476                                         constants._user_settings_,
477                                         e.section,
478                                 ),
479                         )
480
481                 for backendId, view in itertools.chain(
482                         self._dialpads.iteritems(),
483                         self._accountViews.iteritems(),
484                         self._messagesViews.iteritems(),
485                         self._recentViews.iteritems(),
486                         self._contactsViews.iteritems(),
487                 ):
488                         sectionName = "%s - %s" % (backendId, view.name())
489                         try:
490                                 view.load_settings(config, sectionName)
491                         except ConfigParser.NoOptionError, e:
492                                 logging.exception(
493                                         "Settings file %s is missing section %s" % (
494                                                 constants._user_settings_,
495                                                 e.section,
496                                         ),
497                                 )
498                         except ConfigParser.NoSectionError, e:
499                                 logging.exception(
500                                         "Settings file %s is missing section %s" % (
501                                                 constants._user_settings_,
502                                                 e.section,
503                                         ),
504                                 )
505
506                 # @todo down here till this issue is fixed
507                 try:
508                         previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
509                         if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
510                                 hildonize.window_to_landscape(self._window)
511                         elif previousOrientation == gtk.ORIENTATION_VERTICAL:
512                                 hildonize.window_to_portrait(self._window)
513                 except ConfigParser.NoOptionError, e:
514                         logging.exception(
515                                 "Settings file %s is missing section %s" % (
516                                         constants._user_settings_,
517                                         e.section,
518                                 ),
519                         )
520                 except ConfigParser.NoSectionError, e:
521                         logging.exception(
522                                 "Settings file %s is missing section %s" % (
523                                         constants._user_settings_,
524                                         e.section,
525                                 ),
526                         )
527
528         def save_settings(self, config):
529                 """
530                 @note Thread Agnostic
531                 """
532                 config.add_section(constants.__pretty_app_name__)
533                 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
534                 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
535                 for i, value in enumerate(self._credentials):
536                         blob = base64.b64encode(value)
537                         config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
538                 config.add_section("alarm")
539                 if self._alarmHandler is not None:
540                         self._alarmHandler.save_settings(config, "alarm")
541
542                 for backendId, view in itertools.chain(
543                         self._dialpads.iteritems(),
544                         self._accountViews.iteritems(),
545                         self._messagesViews.iteritems(),
546                         self._recentViews.iteritems(),
547                         self._contactsViews.iteritems(),
548                 ):
549                         sectionName = "%s - %s" % (backendId, view.name())
550                         config.add_section(sectionName)
551                         view.save_settings(config, sectionName)
552
553         def _save_settings(self):
554                 """
555                 @note Thread Agnostic
556                 """
557                 config = ConfigParser.SafeConfigParser()
558                 self.save_settings(config)
559                 with open(constants._user_settings_, "wb") as configFile:
560                         config.write(configFile)
561
562         def _refresh_active_tab(self):
563                 pageIndex = self._notebook.get_current_page()
564                 if pageIndex == self.CONTACTS_TAB:
565                         self._contactsViews[self._selectedBackendId].update(force=True)
566                 elif pageIndex == self.RECENT_TAB:
567                         self._recentViews[self._selectedBackendId].update(force=True)
568                 elif pageIndex == self.MESSAGES_TAB:
569                         self._messagesViews[self._selectedBackendId].update(force=True)
570
571                 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
572                         if self._ledHandler is not None:
573                                 self._ledHandler.off()
574
575         def _on_close(self, *args, **kwds):
576                 try:
577                         if self._osso is not None:
578                                 self._osso.close()
579
580                         if self._initDone:
581                                 self._save_settings()
582                 finally:
583                         gtk.main_quit()
584
585         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
586                 """
587                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
588                 For system_inactivity, we have no background tasks to pause
589
590                 @note Hildon specific
591                 """
592                 try:
593                         if memory_low:
594                                 for backendId in self.BACKENDS:
595                                         self._phoneBackends[backendId].clear_caches()
596                                 self._contactsViews[self._selectedBackendId].clear_caches()
597                                 gc.collect()
598
599                         if save_unsaved_data or shutdown:
600                                 self._save_settings()
601                 except Exception, e:
602                         self._errorDisplay.push_exception()
603
604         def _on_connection_change(self, connection, event, magicIdentifier):
605                 """
606                 @note Hildon specific
607                 """
608                 try:
609                         import conic
610
611                         status = event.get_status()
612                         error = event.get_error()
613                         iap_id = event.get_iap_id()
614                         bearer = event.get_bearer_type()
615
616                         if status == conic.STATUS_CONNECTED:
617                                 if self._initDone:
618                                         self._spawn_attempt_login(2)
619                         elif status == conic.STATUS_DISCONNECTED:
620                                 if self._initDone:
621                                         self._defaultBackendId = self._selectedBackendId
622                                         self._change_loggedin_status(self.NULL_BACKEND)
623                 except Exception, e:
624                         self._errorDisplay.push_exception()
625
626         def _on_window_state_change(self, widget, event, *args):
627                 """
628                 @note Hildon specific
629                 """
630                 try:
631                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
632                                 self._isFullScreen = True
633                         else:
634                                 self._isFullScreen = False
635                 except Exception, e:
636                         self._errorDisplay.push_exception()
637
638         def _on_key_press(self, widget, event, *args):
639                 """
640                 @note Hildon specific
641                 """
642                 try:
643                         if event.keyval == gtk.keysyms.F6:
644                                 if self._isFullScreen:
645                                         self._window.unfullscreen()
646                                 else:
647                                         self._window.fullscreen()
648                 except Exception, e:
649                         self._errorDisplay.push_exception()
650
651         def _on_clearcookies_clicked(self, *args):
652                 try:
653                         self._phoneBackends[self._selectedBackendId].logout()
654                         self._accountViews[self._selectedBackendId].clear()
655                         self._recentViews[self._selectedBackendId].clear()
656                         self._messagesViews[self._selectedBackendId].clear()
657                         self._contactsViews[self._selectedBackendId].clear()
658                         self._change_loggedin_status(self.NULL_BACKEND)
659
660                         self._spawn_attempt_login(2, True)
661                 except Exception, e:
662                         self._errorDisplay.push_exception()
663
664         def _on_notebook_switch_page(self, notebook, page, pageIndex):
665                 try:
666                         self._reset_tab_refresh()
667
668                         didRecentUpdate = False
669                         didMessagesUpdate = False
670
671                         if pageIndex == self.RECENT_TAB:
672                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
673                         elif pageIndex == self.MESSAGES_TAB:
674                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
675                         elif pageIndex == self.CONTACTS_TAB:
676                                 self._contactsViews[self._selectedBackendId].update()
677                         elif pageIndex == self.ACCOUNT_TAB:
678                                 self._accountViews[self._selectedBackendId].update()
679
680                         if didRecentUpdate or didMessagesUpdate:
681                                 if self._ledHandler is not None:
682                                         self._ledHandler.off()
683                 except Exception, e:
684                         self._errorDisplay.push_exception()
685
686         def _set_tab_refresh(self, *args):
687                 try:
688                         pageIndex = self._notebook.get_current_page()
689                         child = self._notebook.get_nth_page(pageIndex)
690                         self._notebook.get_tab_label(child).set_text("Refresh?")
691                 except Exception, e:
692                         self._errorDisplay.push_exception()
693                 return False
694
695         def _reset_tab_refresh(self, *args):
696                 try:
697                         pageIndex = self._notebook.get_current_page()
698                         child = self._notebook.get_nth_page(pageIndex)
699                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
700                 except Exception, e:
701                         self._errorDisplay.push_exception()
702                 return False
703
704         def _on_tab_refresh(self, *args):
705                 try:
706                         self._refresh_active_tab()
707                         self._reset_tab_refresh()
708                 except Exception, e:
709                         self._errorDisplay.push_exception()
710                 return False
711
712         def _on_sms_clicked(self, number, message):
713                 try:
714                         assert number, "No number specified"
715                         assert message, "Empty message"
716                         try:
717                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
718                         except Exception, e:
719                                 loggedIn = False
720                                 self._errorDisplay.push_exception()
721                                 return
722
723                         if not loggedIn:
724                                 self._errorDisplay.push_message(
725                                         "Backend link with grandcentral is not working, please try again"
726                                 )
727                                 return
728
729                         dialed = False
730                         try:
731                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
732                                 dialed = True
733                         except Exception, e:
734                                 self._errorDisplay.push_exception()
735
736                         if dialed:
737                                 self._dialpads[self._selectedBackendId].clear()
738                 except Exception, e:
739                         self._errorDisplay.push_exception()
740
741         def _on_dial_clicked(self, number):
742                 try:
743                         assert number, "No number to call"
744                         try:
745                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
746                         except Exception, e:
747                                 loggedIn = False
748                                 self._errorDisplay.push_exception()
749                                 return
750
751                         if not loggedIn:
752                                 self._errorDisplay.push_message(
753                                         "Backend link with grandcentral is not working, please try again"
754                                 )
755                                 return
756
757                         dialed = False
758                         try:
759                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
760                                 self._phoneBackends[self._selectedBackendId].dial(number)
761                                 dialed = True
762                         except Exception, e:
763                                 self._errorDisplay.push_exception()
764
765                         if dialed:
766                                 self._dialpads[self._selectedBackendId].clear()
767                 except Exception, e:
768                         self._errorDisplay.push_exception()
769
770         def _on_menu_refresh(self, *args):
771                 try:
772                         self._refresh_active_tab()
773                 except Exception, e:
774                         self._errorDisplay.push_exception()
775
776         def _on_menu_rotate(self, *args):
777                 try:
778                         orientation = gtk_toolbox.get_screen_orientation()
779                         if orientation == gtk.ORIENTATION_HORIZONTAL:
780                                 hildonize.window_to_portrait(self._window)
781                         elif orientation == gtk.ORIENTATION_VERTICAL:
782                                 hildonize.window_to_landscape(self._window)
783                 except Exception, e:
784                         self._errorDisplay.push_exception()
785
786         def _on_paste(self, *args):
787                 try:
788                         contents = self._clipboard.wait_for_text()
789                         self._dialpads[self._selectedBackendId].set_number(contents)
790                 except Exception, e:
791                         self._errorDisplay.push_exception()
792
793         def _on_about_activate(self, *args):
794                 try:
795                         dlg = gtk.AboutDialog()
796                         dlg.set_name(constants.__pretty_app_name__)
797                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
798                         dlg.set_copyright("Copyright 2008 - LGPL")
799                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice/Grandcentral account.  This application is not affiliated with Google in any way")
800                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
801                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
802                         dlg.run()
803                         dlg.destroy()
804                 except Exception, e:
805                         self._errorDisplay.push_exception()
806
807
808 def run_doctest():
809         import doctest
810
811         failureCount, testCount = doctest.testmod()
812         if not failureCount:
813                 print "Tests Successful"
814                 sys.exit(0)
815         else:
816                 sys.exit(1)
817
818
819 def run_dialpad():
820         _lock_file = os.path.join(constants._data_path_, ".lock")
821         #with gtk_toolbox.flock(_lock_file, 0):
822         gtk.gdk.threads_init()
823
824         if hildonize.IS_HILDON:
825                 gtk.set_application_name(constants.__pretty_app_name__)
826         handle = Dialcentral()
827         gtk.main()
828
829
830 class DummyOptions(object):
831
832         def __init__(self):
833                 self.test = False
834
835
836 if __name__ == "__main__":
837         logging.basicConfig(level=logging.DEBUG)
838         try:
839                 if len(sys.argv) > 1:
840                         try:
841                                 import optparse
842                         except ImportError:
843                                 optparse = None
844
845                         if optparse is not None:
846                                 parser = optparse.OptionParser()
847                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
848                                 (commandOptions, commandArgs) = parser.parse_args()
849                 else:
850                         commandOptions = DummyOptions()
851                         commandArgs = []
852
853                 if commandOptions.test:
854                         run_doctest()
855                 else:
856                         run_dialpad()
857         finally:
858                 logging.shutdown()