Random tweak, see changelist for details
[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 scrollingWidget in (
119                         'recent_scrolledwindow',
120                         'message_scrolledwindow',
121                         'contacts_scrolledwindow',
122                         "phoneSelectionMessages_scrolledwindow",
123                         "smsMessages_scrolledwindow",
124                 ):
125                         hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
126                 for scrollingWidget in (
127                         "phonetypes_scrolledwindow",
128                         "smsMessage_scrolledEntry",
129                 ):
130                         hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
131
132                 for button in (
133                         "back",
134                         "sendSmsButton",
135                         "cancelSmsButton",
136                         "sms_button",
137                         "dial_button",
138                         "select_button",
139                         "cancel_button",
140                         "minutesEntryButton",
141                         "clearcookies",
142                 ):
143                         hildonize.set_button_thumb_selectable(button)
144
145                 replacementButtons = [gtk.Button("Test")]
146                 menu = hildonize.hildonize_menu(
147                         self._window,
148                         self._widgetTree.get_widget("dialpad_menubar"),
149                         replacementButtons
150                 )
151
152                 self._window.connect("key-press-event", self._on_key_press)
153                 self._window.connect("window-state-event", self._on_window_state_change)
154                 if not hildonize.IS_HILDON_SUPPORTED:
155                         _moduleLogger.warning("No hildonization support")
156
157                 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
158
159                 self._window.connect("destroy", self._on_close)
160                 self._window.set_default_size(800, 300)
161                 self._window.show_all()
162
163                 self._loginSink = gtk_toolbox.threaded_stage(
164                         gtk_toolbox.comap(
165                                 self._attempt_login,
166                                 gtk_toolbox.null_sink(),
167                         )
168                 )
169
170                 backgroundSetup = threading.Thread(target=self._idle_setup)
171                 backgroundSetup.setDaemon(True)
172                 backgroundSetup.start()
173
174         def _idle_setup(self):
175                 """
176                 If something can be done after the UI loads, push it here so it's not blocking the UI
177                 """
178                 # Barebones UI handlers
179                 try:
180                         import null_backend
181                         import null_views
182
183                         self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
184                         with gtk_toolbox.gtk_lock():
185                                 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
186                                 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
187                                 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
188                                 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
189                                 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
190
191                                 self._dialpads[self._selectedBackendId].enable()
192                                 self._accountViews[self._selectedBackendId].enable()
193                                 self._recentViews[self._selectedBackendId].enable()
194                                 self._messagesViews[self._selectedBackendId].enable()
195                                 self._contactsViews[self._selectedBackendId].enable()
196                 except Exception, e:
197                         with gtk_toolbox.gtk_lock():
198                                 self._errorDisplay.push_exception()
199
200                 # Setup maemo specifics
201                 try:
202                         try:
203                                 import osso
204                         except (ImportError, OSError):
205                                 osso = None
206                         self._osso = None
207                         if osso is not None:
208                                 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
209                                 device = osso.DeviceState(self._osso)
210                                 device.set_device_state_callback(self._on_device_state_change, 0)
211                         else:
212                                 _moduleLogger.warning("No device state support")
213
214                         try:
215                                 import alarm_handler
216                                 self._alarmHandler = alarm_handler.AlarmHandler()
217                         except (ImportError, OSError):
218                                 alarm_handler = None
219                         except Exception:
220                                 with gtk_toolbox.gtk_lock():
221                                         self._errorDisplay.push_exception()
222                                 alarm_handler = None
223                                 _moduleLogger.warning("No notification support")
224                         if hildonize.IS_HILDON_SUPPORTED:
225                                 try:
226                                         import led_handler
227                                         self._ledHandler = led_handler.LedHandler()
228                                 except Exception, e:
229                                         _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
230                                         self._ledHandler = None
231                         else:
232                                 self._ledHandler = None
233
234                         try:
235                                 import conic
236                         except (ImportError, OSError):
237                                 conic = None
238                         self._connection = None
239                         if conic is not None:
240                                 self._connection = conic.Connection()
241                                 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
242                                 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
243                         else:
244                                 _moduleLogger.warning("No connection support")
245                 except Exception, e:
246                         with gtk_toolbox.gtk_lock():
247                                 self._errorDisplay.push_exception()
248
249                 # Setup costly backends
250                 try:
251                         import gv_backend
252                         import file_backend
253                         import gv_views
254
255                         try:
256                                 os.makedirs(constants._data_path_)
257                         except OSError, e:
258                                 if e.errno != 17:
259                                         raise
260                         gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
261
262                         self._phoneBackends.update({
263                                 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
264                         })
265                         with gtk_toolbox.gtk_lock():
266                                 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
267                                 self._dialpads.update({
268                                         self.GV_BACKEND: unifiedDialpad,
269                                 })
270                                 self._accountViews.update({
271                                         self.GV_BACKEND: gv_views.AccountInfo(
272                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
273                                         ),
274                                 })
275                                 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
276                                 self._recentViews.update({
277                                         self.GV_BACKEND: gv_views.RecentCallsView(
278                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
279                                         ),
280                                 })
281                                 self._messagesViews.update({
282                                         self.GV_BACKEND: gv_views.MessagesView(
283                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
284                                         ),
285                                 })
286                                 self._contactsViews.update({
287                                         self.GV_BACKEND: gv_views.ContactsView(
288                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
289                                         ),
290                                 })
291
292                         fsContactsPath = os.path.join(constants._data_path_, "contacts")
293                         fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
294
295                         self._dialpads[self.GV_BACKEND].number_selected = self._select_action
296                         self._recentViews[self.GV_BACKEND].number_selected = self._select_action
297                         self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
298                         self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
299
300                         addressBooks = [
301                                 self._phoneBackends[self.GV_BACKEND],
302                                 fileBackend,
303                         ]
304                         mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
305                         self._contactsViews[self.GV_BACKEND].append(mergedBook)
306                         self._contactsViews[self.GV_BACKEND].extend(addressBooks)
307                         self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
308
309                         callbackMapping = {
310                                 "on_paste": self._on_paste,
311                                 "on_refresh": self._on_menu_refresh,
312                                 "on_clearcookies_clicked": self._on_clearcookies_clicked,
313                                 "on_about_activate": self._on_about_activate,
314                         }
315                         if hildonize.GTK_MENU_USED:
316                                 self._widgetTree.signal_autoconnect(callbackMapping)
317                         self._notebook.connect("switch-page", self._on_notebook_switch_page)
318                         self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
319
320                         with gtk_toolbox.gtk_lock():
321                                 self._originalCurrentLabels = [
322                                         self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
323                                         for pageIndex in xrange(self._notebook.get_n_pages())
324                                 ]
325                                 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
326                                 self._notebookTapHandler.enable()
327                         self._notebookTapHandler.on_tap = self._reset_tab_refresh
328                         self._notebookTapHandler.on_hold = self._on_tab_refresh
329                         self._notebookTapHandler.on_holding = self._set_tab_refresh
330                         self._notebookTapHandler.on_cancel = self._reset_tab_refresh
331
332                         self._initDone = True
333
334                         config = ConfigParser.SafeConfigParser()
335                         config.read(constants._user_settings_)
336                         with gtk_toolbox.gtk_lock():
337                                 self.load_settings(config)
338                 except Exception, e:
339                         with gtk_toolbox.gtk_lock():
340                                 self._errorDisplay.push_exception()
341                 finally:
342                         self._spawn_attempt_login(2)
343
344         def _spawn_attempt_login(self, *args):
345                 self._loginSink.send(args)
346
347         def _attempt_login(self, numOfAttempts = 10, force = False):
348                 """
349                 @note This must be run outside of the UI lock
350                 """
351                 try:
352                         assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
353                         assert self._initDone, "Attempting login before app is fully loaded"
354
355                         serviceId = self.NULL_BACKEND
356                         loggedIn = False
357                         if not force:
358                                 with gtk_toolbox.gtk_lock():
359                                         banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
360                                 try:
361                                         self.refresh_session()
362                                         serviceId = self._defaultBackendId
363                                         loggedIn = True
364                                 except Exception, e:
365                                         _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
366                                 finally:
367                                         with gtk_toolbox.gtk_lock():
368                                                 hildonize.show_busy_banner_end(banner)
369
370                         if not loggedIn:
371                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
372
373                         with gtk_toolbox.gtk_lock():
374                                 self._change_loggedin_status(serviceId)
375                                 if loggedIn:
376                                         hildonize.show_information_banner(self._window, "Logged In")
377                                 else:
378                                         hildonize.show_information_banner(self._window, "Login Failed")
379                 except Exception, e:
380                         with gtk_toolbox.gtk_lock():
381                                 self._errorDisplay.push_exception()
382
383         def refresh_session(self):
384                 """
385                 @note Thread agnostic
386                 """
387                 assert self._initDone, "Attempting login before app is fully loaded"
388
389                 loggedIn = False
390                 if not loggedIn:
391                         loggedIn = self._login_by_cookie()
392                 if not loggedIn:
393                         loggedIn = self._login_by_settings()
394
395                 if not loggedIn:
396                         raise RuntimeError("Login Failed")
397
398         def _login_by_cookie(self):
399                 """
400                 @note Thread agnostic
401                 """
402                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
403                 if loggedIn:
404                         _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
405                 return loggedIn
406
407         def _login_by_settings(self):
408                 """
409                 @note Thread agnostic
410                 """
411                 username, password = self._credentials
412                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
413                 if loggedIn:
414                         self._credentials = username, password
415                         _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
416                 return loggedIn
417
418         def _login_by_user(self, numOfAttempts):
419                 """
420                 @note This must be run outside of the UI lock
421                 """
422                 loggedIn, (username, password) = False, self._credentials
423                 tmpServiceId = self.GV_BACKEND
424                 for attemptCount in xrange(numOfAttempts):
425                         if loggedIn:
426                                 break
427                         with gtk_toolbox.gtk_lock():
428                                 credentials = self._credentialsDialog.request_credentials(
429                                         defaultCredentials = self._credentials
430                                 )
431                                 if not self._phoneBackends[tmpServiceId].get_callback_number():
432                                         # subtle reminder to the users to configure things
433                                         self._notebook.set_current_page(self.ACCOUNT_TAB)
434                                 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
435                         try:
436                                 username, password = credentials
437                                 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
438                         finally:
439                                 with gtk_toolbox.gtk_lock():
440                                         hildonize.show_busy_banner_end(banner)
441
442                 if loggedIn:
443                         serviceId = tmpServiceId
444                         self._credentials = username, password
445                         _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
446                 else:
447                         serviceId = self.NULL_BACKEND
448                         self._notebook.set_current_page(self.ACCOUNT_TAB)
449
450                 return loggedIn, serviceId
451
452         def _select_action(self, action, number, message):
453                 self.refresh_session()
454                 if action == "select":
455                         self._dialpads[self._selectedBackendId].set_number(number)
456                         self._notebook.set_current_page(self.KEYPAD_TAB)
457                 elif 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(2)
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(2, 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()