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