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