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