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