Adding saving of fullscreen status to settings, needed way to quit on Ubuntu so added...
[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
541                         isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
542                         if isFullscreen:
543                                 self._window.fullscreen()
544                 except ConfigParser.NoOptionError, e:
545                         _moduleLogger.exception(
546                                 "Settings file %s is missing section %s" % (
547                                         constants._user_settings_,
548                                         e.section,
549                                 ),
550                         )
551                 except ConfigParser.NoSectionError, e:
552                         _moduleLogger.exception(
553                                 "Settings file %s is missing section %s" % (
554                                         constants._user_settings_,
555                                         e.section,
556                                 ),
557                         )
558
559                 for backendId, view in itertools.chain(
560                         self._dialpads.iteritems(),
561                         self._accountViews.iteritems(),
562                         self._messagesViews.iteritems(),
563                         self._historyViews.iteritems(),
564                         self._contactsViews.iteritems(),
565                 ):
566                         sectionName = "%s - %s" % (backendId, view.name())
567                         try:
568                                 view.load_settings(config, sectionName)
569                         except ConfigParser.NoOptionError, e:
570                                 _moduleLogger.exception(
571                                         "Settings file %s is missing section %s" % (
572                                                 constants._user_settings_,
573                                                 e.section,
574                                         ),
575                                 )
576                         except ConfigParser.NoSectionError, e:
577                                 _moduleLogger.exception(
578                                         "Settings file %s is missing section %s" % (
579                                                 constants._user_settings_,
580                                                 e.section,
581                                         ),
582                                 )
583
584                 try:
585                         previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
586                         if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
587                                 hildonize.window_to_landscape(self._window)
588                         elif previousOrientation == gtk.ORIENTATION_VERTICAL:
589                                 hildonize.window_to_portrait(self._window)
590                 except ConfigParser.NoOptionError, e:
591                         _moduleLogger.exception(
592                                 "Settings file %s is missing section %s" % (
593                                         constants._user_settings_,
594                                         e.section,
595                                 ),
596                         )
597                 except ConfigParser.NoSectionError, e:
598                         _moduleLogger.exception(
599                                 "Settings file %s is missing section %s" % (
600                                         constants._user_settings_,
601                                         e.section,
602                                 ),
603                         )
604
605         def save_settings(self, config):
606                 """
607                 @note Thread Agnostic
608                 """
609                 # Because we now only support GVoice, if there are user credentials,
610                 # always assume its using the GVoice backend
611                 if self._credentials[0] and self._credentials[1]:
612                         backend = self.GV_BACKEND
613                 else:
614                         backend = self.NULL_BACKEND
615
616                 config.add_section(constants.__pretty_app_name__)
617                 config.set(constants.__pretty_app_name__, "active", str(backend))
618                 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
619                 config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen))
620                 for i, value in enumerate(self._credentials):
621                         blob = base64.b64encode(value)
622                         config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
623                 config.add_section("alarm")
624                 if self._alarmHandler is not None:
625                         self._alarmHandler.save_settings(config, "alarm")
626
627                 for backendId, view in itertools.chain(
628                         self._dialpads.iteritems(),
629                         self._accountViews.iteritems(),
630                         self._messagesViews.iteritems(),
631                         self._historyViews.iteritems(),
632                         self._contactsViews.iteritems(),
633                 ):
634                         sectionName = "%s - %s" % (backendId, view.name())
635                         config.add_section(sectionName)
636                         view.save_settings(config, sectionName)
637
638         def _save_settings(self):
639                 """
640                 @note Thread Agnostic
641                 """
642                 config = ConfigParser.SafeConfigParser()
643                 self.save_settings(config)
644                 with open(constants._user_settings_, "wb") as configFile:
645                         config.write(configFile)
646
647         def _refresh_active_tab(self):
648                 pageIndex = self._notebook.get_current_page()
649                 if pageIndex == self.CONTACTS_TAB:
650                         self._contactsViews[self._selectedBackendId].update(force=True)
651                 elif pageIndex == self.RECENT_TAB:
652                         self._historyViews[self._selectedBackendId].update(force=True)
653                 elif pageIndex == self.MESSAGES_TAB:
654                         self._messagesViews[self._selectedBackendId].update(force=True)
655
656                 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
657                         if self._ledHandler is not None:
658                                 self._ledHandler.off()
659
660         @gtk_toolbox.log_exception(_moduleLogger)
661         def _on_close(self, *args, **kwds):
662                 try:
663                         if self._initDone:
664                                 self._save_settings()
665
666                         try:
667                                 self._deviceState.close()
668                         except AttributeError:
669                                 pass # Either None or close was removed (in Fremantle)
670                         try:
671                                 self._osso.close()
672                         except AttributeError:
673                                 pass # Either None or close was removed (in Fremantle)
674                 finally:
675                         gtk.main_quit()
676
677         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
678                 """
679                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
680                 For system_inactivity, we have no background tasks to pause
681
682                 @note Hildon specific
683                 """
684                 try:
685                         if memory_low:
686                                 for backendId in self.BACKENDS:
687                                         self._phoneBackends[backendId].clear_caches()
688                                 self._contactsViews[self._selectedBackendId].clear_caches()
689                                 gc.collect()
690
691                         if save_unsaved_data or shutdown:
692                                 self._save_settings()
693                 except Exception, e:
694                         self._errorDisplay.push_exception()
695
696         def _on_connection_change(self, connection, event, magicIdentifier):
697                 """
698                 @note Hildon specific
699                 """
700                 try:
701                         import conic
702
703                         status = event.get_status()
704                         error = event.get_error()
705                         iap_id = event.get_iap_id()
706                         bearer = event.get_bearer_type()
707
708                         if status == conic.STATUS_CONNECTED:
709                                 if self._initDone:
710                                         self._spawn_attempt_login()
711                         elif status == conic.STATUS_DISCONNECTED:
712                                 if self._initDone:
713                                         self._defaultBackendId = self._selectedBackendId
714                                         self._change_loggedin_status(self.NULL_BACKEND)
715                 except Exception, e:
716                         self._errorDisplay.push_exception()
717
718         def _on_window_state_change(self, widget, event, *args):
719                 """
720                 @note Hildon specific
721                 """
722                 try:
723                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
724                                 self._isFullScreen = True
725                         else:
726                                 self._isFullScreen = False
727                 except Exception, e:
728                         self._errorDisplay.push_exception()
729
730         def _on_key_press(self, widget, event, *args):
731                 """
732                 @note Hildon specific
733                 """
734                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
735                 try:
736                         if (
737                                 event.keyval == gtk.keysyms.F6 or
738                                 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
739                         ):
740                                 if self._isFullScreen:
741                                         self._window.unfullscreen()
742                                 else:
743                                         self._window.fullscreen()
744                         elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK:
745                                 with open(constants._user_logpath_, "r") as f:
746                                         logLines = f.xreadlines()
747                                         log = "".join(logLines)
748                                         self._clipboard.set_text(str(log))
749                         elif event.keyval in (ord("w"), ord("q")) and event.get_state() & gtk.gdk.CONTROL_MASK:
750                                 self._window.destroy()
751                         elif event.keyval == ord("r") and event.get_state() & gtk.gdk.CONTROL_MASK:
752                                 self._refresh_active_tab()
753                 except Exception, e:
754                         self._errorDisplay.push_exception()
755
756         def _on_clearcookies_clicked(self, *args):
757                 try:
758                         self._phoneBackends[self._selectedBackendId].logout()
759                         self._accountViews[self._selectedBackendId].clear()
760                         self._historyViews[self._selectedBackendId].clear()
761                         self._messagesViews[self._selectedBackendId].clear()
762                         self._contactsViews[self._selectedBackendId].clear()
763                         self._change_loggedin_status(self.NULL_BACKEND)
764
765                         self._spawn_attempt_login(True)
766                 except Exception, e:
767                         self._errorDisplay.push_exception()
768
769         def _on_notebook_switch_page(self, notebook, page, pageIndex):
770                 try:
771                         self._reset_tab_refresh()
772
773                         didRecentUpdate = False
774                         didMessagesUpdate = False
775
776                         if pageIndex == self.RECENT_TAB:
777                                 didRecentUpdate = self._historyViews[self._selectedBackendId].update()
778                         elif pageIndex == self.MESSAGES_TAB:
779                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
780                         elif pageIndex == self.CONTACTS_TAB:
781                                 self._contactsViews[self._selectedBackendId].update()
782                         elif pageIndex == self.ACCOUNT_TAB:
783                                 self._accountViews[self._selectedBackendId].update()
784
785                         if didRecentUpdate or didMessagesUpdate:
786                                 if self._ledHandler is not None:
787                                         self._ledHandler.off()
788                 except Exception, e:
789                         self._errorDisplay.push_exception()
790
791         def _set_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("Refresh?")
796                 except Exception, e:
797                         self._errorDisplay.push_exception()
798                 return False
799
800         def _reset_tab_refresh(self, *args):
801                 try:
802                         pageIndex = self._notebook.get_current_page()
803                         child = self._notebook.get_nth_page(pageIndex)
804                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
805                 except Exception, e:
806                         self._errorDisplay.push_exception()
807                 return False
808
809         def _on_tab_refresh(self, *args):
810                 try:
811                         self._refresh_active_tab()
812                         self._reset_tab_refresh()
813                 except Exception, e:
814                         self._errorDisplay.push_exception()
815                 return False
816
817         def _on_sms_clicked(self, number, message):
818                 try:
819                         assert number, "No number specified"
820                         assert message, "Empty message"
821                         try:
822                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
823                         except Exception, e:
824                                 loggedIn = False
825                                 self._errorDisplay.push_exception()
826                                 return
827
828                         if not loggedIn:
829                                 self._errorDisplay.push_message(
830                                         "Backend link with GoogleVoice is not working, please try again"
831                                 )
832                                 return
833
834                         dialed = False
835                         try:
836                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
837                                 hildonize.show_information_banner(self._window, "Sending to %s" % number)
838                                 _moduleLogger.info("Sending SMS to %s" % number)
839                                 dialed = True
840                         except Exception, e:
841                                 self._errorDisplay.push_exception()
842
843                         if dialed:
844                                 self._dialpads[self._selectedBackendId].clear()
845                 except Exception, e:
846                         self._errorDisplay.push_exception()
847
848         def _on_dial_clicked(self, number):
849                 try:
850                         assert number, "No number to call"
851                         try:
852                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
853                         except Exception, e:
854                                 loggedIn = False
855                                 self._errorDisplay.push_exception()
856                                 return
857
858                         if not loggedIn:
859                                 self._errorDisplay.push_message(
860                                         "Backend link with GoogleVoice is not working, please try again"
861                                 )
862                                 return
863
864                         dialed = False
865                         try:
866                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
867                                 self._phoneBackends[self._selectedBackendId].call(number)
868                                 hildonize.show_information_banner(self._window, "Calling %s" % number)
869                                 _moduleLogger.info("Calling %s" % number)
870                                 dialed = True
871                         except Exception, e:
872                                 self._errorDisplay.push_exception()
873
874                         if dialed:
875                                 self._dialpads[self._selectedBackendId].clear()
876                 except Exception, e:
877                         self._errorDisplay.push_exception()
878
879         def _on_menu_refresh(self, *args):
880                 try:
881                         self._refresh_active_tab()
882                 except Exception, e:
883                         self._errorDisplay.push_exception()
884
885         def _on_paste(self, *args):
886                 try:
887                         contents = self._clipboard.wait_for_text()
888                         if contents is not None:
889                                 self._dialpads[self._selectedBackendId].set_number(contents)
890                 except Exception, e:
891                         self._errorDisplay.push_exception()
892
893         def _on_about_activate(self, *args):
894                 try:
895                         dlg = gtk.AboutDialog()
896                         dlg.set_name(constants.__pretty_app_name__)
897                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
898                         dlg.set_copyright("Copyright 2008 - LGPL")
899                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account.  This application is not affiliated with Google in any way")
900                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
901                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <eopage@byu.net>"])
902                         dlg.run()
903                         dlg.destroy()
904                 except Exception, e:
905                         self._errorDisplay.push_exception()
906
907
908 def run_doctest():
909         import doctest
910
911         failureCount, testCount = doctest.testmod()
912         if not failureCount:
913                 print "Tests Successful"
914                 sys.exit(0)
915         else:
916                 sys.exit(1)
917
918
919 def run_dialpad():
920         _lock_file = os.path.join(constants._data_path_, ".lock")
921         #with gtk_toolbox.flock(_lock_file, 0):
922         gtk.gdk.threads_init()
923
924         if hildonize.IS_HILDON_SUPPORTED:
925                 gtk.set_application_name(constants.__pretty_app_name__)
926         handle = Dialcentral()
927         if not PROFILE_STARTUP:
928                 gtk.main()
929
930
931 class DummyOptions(object):
932
933         def __init__(self):
934                 self.test = False
935
936
937 if __name__ == "__main__":
938         logging.basicConfig(level=logging.DEBUG)
939         try:
940                 if len(sys.argv) > 1:
941                         try:
942                                 import optparse
943                         except ImportError:
944                                 optparse = None
945
946                         if optparse is not None:
947                                 parser = optparse.OptionParser()
948                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
949                                 (commandOptions, commandArgs) = parser.parse_args()
950                 else:
951                         commandOptions = DummyOptions()
952                         commandArgs = []
953
954                 if commandOptions.test:
955                         run_doctest()
956                 else:
957                         run_dialpad()
958         finally:
959                 logging.shutdown()