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