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