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