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