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