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