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