Bug fixes and adding of user notifications
[gc-dialer] / src / dc_glade.py
1 #!/usr/bin/python2.5
2
3 """
4 DialCentral - Front end for Google's Grand Central 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 @bug When switching to tab while logging in, it doesn't refresh once logged int
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 def getmtime_nothrow(path):
45         try:
46                 return os.path.getmtime(path)
47         except Exception:
48                 return 0
49
50
51 def display_error_message(msg):
52         error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
53
54         def close(dialog, response):
55                 dialog.destroy()
56         error_dialog.connect("response", close)
57         error_dialog.run()
58
59
60 class Dialcentral(object):
61
62         _glade_files = [
63                 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
64                 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
65                 '/usr/lib/dialcentral/dialcentral.glade',
66         ]
67
68         KEYPAD_TAB = 0
69         RECENT_TAB = 1
70         MESSAGES_TAB = 2
71         CONTACTS_TAB = 3
72         ACCOUNT_TAB = 4
73
74         NULL_BACKEND = 0
75         GV_BACKEND = 2
76         BACKENDS = (NULL_BACKEND, GV_BACKEND)
77
78         def __init__(self):
79                 self._initDone = False
80                 self._connection = None
81                 self._osso = None
82                 self._clipboard = gtk.clipboard_get()
83
84                 self._credentials = ("", "")
85                 self._selectedBackendId = self.NULL_BACKEND
86                 self._defaultBackendId = self.GV_BACKEND
87                 self._phoneBackends = None
88                 self._dialpads = None
89                 self._accountViews = None
90                 self._messagesViews = None
91                 self._recentViews = None
92                 self._contactsViews = None
93                 self._alarmHandler = None
94                 self._ledHandler = None
95                 self._originalCurrentLabels = []
96
97                 for path in self._glade_files:
98                         if os.path.isfile(path):
99                                 self._widgetTree = gtk.glade.XML(path)
100                                 break
101                 else:
102                         display_error_message("Cannot find dialcentral.glade")
103                         gtk.main_quit()
104                         return
105
106                 self._window = self._widgetTree.get_widget("mainWindow")
107                 self._notebook = self._widgetTree.get_widget("notebook")
108                 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
109                 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
110
111                 self._isFullScreen = False
112                 self._app = hildonize.get_app_class()()
113                 self._window = hildonize.hildonize_window(self._app, self._window)
114                 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
115                 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
116                 hildonize.hildonize_combo_entry(self._widgetTree.get_widget("callbackcombo").get_child())
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                 if hildonize.IS_HILDON_SUPPORTED:
140                         self._window.connect("key-press-event", self._on_key_press)
141                         self._window.connect("window-state-event", self._on_window_state_change)
142                 else:
143                         logging.warning("No hildonization support")
144
145                 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
146
147                 self._window.connect("destroy", self._on_close)
148                 self._window.set_default_size(800, 300)
149                 self._window.show_all()
150
151                 self._loginSink = gtk_toolbox.threaded_stage(
152                         gtk_toolbox.comap(
153                                 self._attempt_login,
154                                 gtk_toolbox.null_sink(),
155                         )
156                 )
157
158                 backgroundSetup = threading.Thread(target=self._idle_setup)
159                 backgroundSetup.setDaemon(True)
160                 backgroundSetup.start()
161
162         def _idle_setup(self):
163                 """
164                 If something can be done after the UI loads, push it here so it's not blocking the UI
165                 """
166                 # Barebones UI handlers
167                 try:
168                         import null_backend
169                         import null_views
170
171                         self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
172                         with gtk_toolbox.gtk_lock():
173                                 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
174                                 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
175                                 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
176                                 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
177                                 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
178
179                                 self._dialpads[self._selectedBackendId].enable()
180                                 self._accountViews[self._selectedBackendId].enable()
181                                 self._recentViews[self._selectedBackendId].enable()
182                                 self._messagesViews[self._selectedBackendId].enable()
183                                 self._contactsViews[self._selectedBackendId].enable()
184                 except Exception, e:
185                         with gtk_toolbox.gtk_lock():
186                                 self._errorDisplay.push_exception()
187
188                 # Setup maemo specifics
189                 try:
190                         try:
191                                 import osso
192                         except (ImportError, OSError):
193                                 osso = None
194                         self._osso = None
195                         if osso is not None:
196                                 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
197                                 device = osso.DeviceState(self._osso)
198                                 device.set_device_state_callback(self._on_device_state_change, 0)
199                         else:
200                                 logging.warning("No device state support")
201
202                         try:
203                                 import alarm_handler
204                                 self._alarmHandler = alarm_handler.AlarmHandler()
205                         except (ImportError, OSError):
206                                 alarm_handler = None
207                         except Exception:
208                                 with gtk_toolbox.gtk_lock():
209                                         self._errorDisplay.push_exception()
210                                 alarm_handler = None
211                                 logging.warning("No notification support")
212                         if hildonize.IS_HILDON_SUPPORTED:
213                                 try:
214                                         import led_handler
215                                         self._ledHandler = led_handler.LedHandler()
216                                 except Exception, e:
217                                         logging.exception('LED Handling failed: "%s"' % str(e))
218                                         self._ledHandler = None
219                         else:
220                                 self._ledHandler = None
221
222                         try:
223                                 import conic
224                         except (ImportError, OSError):
225                                 conic = None
226                         self._connection = None
227                         if conic is not None:
228                                 self._connection = conic.Connection()
229                                 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
230                                 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
231                         else:
232                                 logging.warning("No connection support")
233                 except Exception, e:
234                         with gtk_toolbox.gtk_lock():
235                                 self._errorDisplay.push_exception()
236
237                 # Setup costly backends
238                 try:
239                         import gv_backend
240                         import file_backend
241                         import gv_views
242
243                         try:
244                                 os.makedirs(constants._data_path_)
245                         except OSError, e:
246                                 if e.errno != 17:
247                                         raise
248                         gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
249
250                         self._phoneBackends.update({
251                                 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
252                         })
253                         with gtk_toolbox.gtk_lock():
254                                 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
255                                 self._dialpads.update({
256                                         self.GV_BACKEND: unifiedDialpad,
257                                 })
258                                 self._accountViews.update({
259                                         self.GV_BACKEND: gv_views.AccountInfo(
260                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
261                                         ),
262                                 })
263                                 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
264                                 self._recentViews.update({
265                                         self.GV_BACKEND: gv_views.RecentCallsView(
266                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
267                                         ),
268                                 })
269                                 self._messagesViews.update({
270                                         self.GV_BACKEND: gv_views.MessagesView(
271                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
272                                         ),
273                                 })
274                                 self._contactsViews.update({
275                                         self.GV_BACKEND: gv_views.ContactsView(
276                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
277                                         ),
278                                 })
279
280                         fsContactsPath = os.path.join(constants._data_path_, "contacts")
281                         fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
282
283                         self._dialpads[self.GV_BACKEND].number_selected = self._select_action
284                         self._recentViews[self.GV_BACKEND].number_selected = self._select_action
285                         self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
286                         self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
287
288                         addressBooks = [
289                                 self._phoneBackends[self.GV_BACKEND],
290                                 fileBackend,
291                         ]
292                         mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
293                         self._contactsViews[self.GV_BACKEND].append(mergedBook)
294                         self._contactsViews[self.GV_BACKEND].extend(addressBooks)
295                         self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
296
297                         callbackMapping = {
298                                 "on_paste": self._on_paste,
299                                 "on_refresh": self._on_menu_refresh,
300                                 "on_clearcookies_clicked": self._on_clearcookies_clicked,
301                                 "on_about_activate": self._on_about_activate,
302                         }
303                         if hildonize.GTK_MENU_USED:
304                                 self._widgetTree.signal_autoconnect(callbackMapping)
305                         self._notebook.connect("switch-page", self._on_notebook_switch_page)
306                         self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
307
308                         with gtk_toolbox.gtk_lock():
309                                 self._originalCurrentLabels = [
310                                         self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
311                                         for pageIndex in xrange(self._notebook.get_n_pages())
312                                 ]
313                                 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
314                                 self._notebookTapHandler.enable()
315                         self._notebookTapHandler.on_tap = self._reset_tab_refresh
316                         self._notebookTapHandler.on_hold = self._on_tab_refresh
317                         self._notebookTapHandler.on_holding = self._set_tab_refresh
318                         self._notebookTapHandler.on_cancel = self._reset_tab_refresh
319
320                         self._initDone = True
321
322                         config = ConfigParser.SafeConfigParser()
323                         config.read(constants._user_settings_)
324                         with gtk_toolbox.gtk_lock():
325                                 self.load_settings(config)
326                 except Exception, e:
327                         with gtk_toolbox.gtk_lock():
328                                 self._errorDisplay.push_exception()
329                 finally:
330                         self._spawn_attempt_login(2)
331
332         def _spawn_attempt_login(self, *args):
333                 self._loginSink.send(args)
334
335         def _attempt_login(self, numOfAttempts = 10, force = False):
336                 """
337                 @todo Handle user notification better like attempting to login and failed login
338
339                 @note This must be run outside of the UI lock
340                 """
341                 try:
342                         assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
343                         assert self._initDone, "Attempting login before app is fully loaded"
344
345                         serviceId = self.NULL_BACKEND
346                         loggedIn = False
347                         if not force:
348                                 try:
349                                         self.refresh_session()
350                                         serviceId = self._defaultBackendId
351                                         loggedIn = True
352                                 except Exception, e:
353                                         logging.exception('Session refresh failed with the following message "%s"' % str(e))
354
355                         if not loggedIn:
356                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
357
358                         with gtk_toolbox.gtk_lock():
359                                 self._change_loggedin_status(serviceId)
360                                 if loggedIn:
361                                         hildonize.show_information_banner(self._window, "Logged In")
362                 except Exception, e:
363                         with gtk_toolbox.gtk_lock():
364                                 self._errorDisplay.push_exception()
365
366         def refresh_session(self):
367                 """
368                 @note Thread agnostic
369                 """
370                 assert self._initDone, "Attempting login before app is fully loaded"
371
372                 loggedIn = False
373                 if not loggedIn:
374                         loggedIn = self._login_by_cookie()
375                 if not loggedIn:
376                         loggedIn = self._login_by_settings()
377
378                 if not loggedIn:
379                         raise RuntimeError("Login Failed")
380
381         def _login_by_cookie(self):
382                 """
383                 @note Thread agnostic
384                 """
385                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
386                 if loggedIn:
387                         logging.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
388                 return loggedIn
389
390         def _login_by_settings(self):
391                 """
392                 @note Thread agnostic
393                 """
394                 username, password = self._credentials
395                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
396                 if loggedIn:
397                         self._credentials = username, password
398                         logging.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
399                 return loggedIn
400
401         def _login_by_user(self, numOfAttempts):
402                 """
403                 @note This must be run outside of the UI lock
404                 """
405                 loggedIn, (username, password) = False, self._credentials
406                 tmpServiceId = self.GV_BACKEND
407                 for attemptCount in xrange(numOfAttempts):
408                         if loggedIn:
409                                 break
410                         with gtk_toolbox.gtk_lock():
411                                 credentials = self._credentialsDialog.request_credentials(
412                                         defaultCredentials = self._credentials
413                                 )
414                                 if not self._phoneBackends[tmpServiceId].get_callback_number():
415                                         # subtle reminder to the users to configure things
416                                         self._notebook.set_current_page(self.ACCOUNT_TAB)
417                         username, password = credentials
418                         loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
419
420                 if loggedIn:
421                         serviceId = tmpServiceId
422                         self._credentials = username, password
423                         logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
424                 else:
425                         serviceId = self.NULL_BACKEND
426                         self._notebook.set_current_page(self.ACCOUNT_TAB)
427
428                 return loggedIn, serviceId
429
430         def _select_action(self, action, number, message):
431                 self.refresh_session()
432                 if action == "select":
433                         self._dialpads[self._selectedBackendId].set_number(number)
434                         self._notebook.set_current_page(self.KEYPAD_TAB)
435                 elif action == "dial":
436                         self._on_dial_clicked(number)
437                 elif action == "sms":
438                         self._on_sms_clicked(number, message)
439                 else:
440                         assert False, "Unknown action: %s" % action
441
442         def _change_loggedin_status(self, newStatus):
443                 oldStatus = self._selectedBackendId
444                 if oldStatus == newStatus:
445                         return
446
447                 self._dialpads[oldStatus].disable()
448                 self._accountViews[oldStatus].disable()
449                 self._recentViews[oldStatus].disable()
450                 self._messagesViews[oldStatus].disable()
451                 self._contactsViews[oldStatus].disable()
452
453                 self._dialpads[newStatus].enable()
454                 self._accountViews[newStatus].enable()
455                 self._recentViews[newStatus].enable()
456                 self._messagesViews[newStatus].enable()
457                 self._contactsViews[newStatus].enable()
458
459                 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
460                         self._phoneBackends[self._selectedBackendId].set_sane_callback()
461                 self._accountViews[self._selectedBackendId].update()
462                 self._refresh_active_tab()
463
464                 self._selectedBackendId = newStatus
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                         logging.exception(
486                                 "Settings file %s is missing section %s" % (
487                                         constants._user_settings_,
488                                         e.section,
489                                 ),
490                         )
491                 except ConfigParser.NoSectionError, e:
492                         logging.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                                 logging.exception(
511                                         "Settings file %s is missing section %s" % (
512                                                 constants._user_settings_,
513                                                 e.section,
514                                         ),
515                                 )
516                         except ConfigParser.NoSectionError, e:
517                                 logging.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                         logging.exception(
532                                 "Settings file %s is missing section %s" % (
533                                         constants._user_settings_,
534                                         e.section,
535                                 ),
536                         )
537                 except ConfigParser.NoSectionError, e:
538                         logging.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 event.keyval == gtk.keysyms.F6:
661                                 if self._isFullScreen:
662                                         self._window.unfullscreen()
663                                 else:
664                                         self._window.fullscreen()
665                 except Exception, e:
666                         self._errorDisplay.push_exception()
667
668         def _on_clearcookies_clicked(self, *args):
669                 try:
670                         self._phoneBackends[self._selectedBackendId].logout()
671                         self._accountViews[self._selectedBackendId].clear()
672                         self._recentViews[self._selectedBackendId].clear()
673                         self._messagesViews[self._selectedBackendId].clear()
674                         self._contactsViews[self._selectedBackendId].clear()
675                         self._change_loggedin_status(self.NULL_BACKEND)
676
677                         self._spawn_attempt_login(2, True)
678                 except Exception, e:
679                         self._errorDisplay.push_exception()
680
681         def _on_notebook_switch_page(self, notebook, page, pageIndex):
682                 try:
683                         self._reset_tab_refresh()
684
685                         didRecentUpdate = False
686                         didMessagesUpdate = False
687
688                         if pageIndex == self.RECENT_TAB:
689                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
690                         elif pageIndex == self.MESSAGES_TAB:
691                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
692                         elif pageIndex == self.CONTACTS_TAB:
693                                 self._contactsViews[self._selectedBackendId].update()
694                         elif pageIndex == self.ACCOUNT_TAB:
695                                 self._accountViews[self._selectedBackendId].update()
696
697                         if didRecentUpdate or didMessagesUpdate:
698                                 if self._ledHandler is not None:
699                                         self._ledHandler.off()
700                 except Exception, e:
701                         self._errorDisplay.push_exception()
702
703         def _set_tab_refresh(self, *args):
704                 try:
705                         pageIndex = self._notebook.get_current_page()
706                         child = self._notebook.get_nth_page(pageIndex)
707                         self._notebook.get_tab_label(child).set_text("Refresh?")
708                 except Exception, e:
709                         self._errorDisplay.push_exception()
710                 return False
711
712         def _reset_tab_refresh(self, *args):
713                 try:
714                         pageIndex = self._notebook.get_current_page()
715                         child = self._notebook.get_nth_page(pageIndex)
716                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
717                 except Exception, e:
718                         self._errorDisplay.push_exception()
719                 return False
720
721         def _on_tab_refresh(self, *args):
722                 try:
723                         self._refresh_active_tab()
724                         self._reset_tab_refresh()
725                 except Exception, e:
726                         self._errorDisplay.push_exception()
727                 return False
728
729         def _on_sms_clicked(self, number, message):
730                 try:
731                         assert number, "No number specified"
732                         assert message, "Empty message"
733                         try:
734                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
735                         except Exception, e:
736                                 loggedIn = False
737                                 self._errorDisplay.push_exception()
738                                 return
739
740                         if not loggedIn:
741                                 self._errorDisplay.push_message(
742                                         "Backend link with grandcentral is not working, please try again"
743                                 )
744                                 return
745
746                         dialed = False
747                         try:
748                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
749                                 hildonize.show_information_banner(self._window, "Sending to %s" % number)
750                                 dialed = True
751                         except Exception, e:
752                                 self._errorDisplay.push_exception()
753
754                         if dialed:
755                                 self._dialpads[self._selectedBackendId].clear()
756                 except Exception, e:
757                         self._errorDisplay.push_exception()
758
759         def _on_dial_clicked(self, number):
760                 try:
761                         assert number, "No number to call"
762                         try:
763                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
764                         except Exception, e:
765                                 loggedIn = False
766                                 self._errorDisplay.push_exception()
767                                 return
768
769                         if not loggedIn:
770                                 self._errorDisplay.push_message(
771                                         "Backend link with grandcentral is not working, please try again"
772                                 )
773                                 return
774
775                         dialed = False
776                         try:
777                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
778                                 self._phoneBackends[self._selectedBackendId].dial(number)
779                                 hildonize.show_information_banner(self._window, "Calling %s" % number)
780                                 dialed = True
781                         except Exception, e:
782                                 self._errorDisplay.push_exception()
783
784                         if dialed:
785                                 self._dialpads[self._selectedBackendId].clear()
786                 except Exception, e:
787                         self._errorDisplay.push_exception()
788
789         def _on_menu_refresh(self, *args):
790                 try:
791                         self._refresh_active_tab()
792                 except Exception, e:
793                         self._errorDisplay.push_exception()
794
795         def _on_paste(self, *args):
796                 try:
797                         contents = self._clipboard.wait_for_text()
798                         if contents is not None:
799                                 self._dialpads[self._selectedBackendId].set_number(contents)
800                 except Exception, e:
801                         self._errorDisplay.push_exception()
802
803         def _on_about_activate(self, *args):
804                 try:
805                         dlg = gtk.AboutDialog()
806                         dlg.set_name(constants.__pretty_app_name__)
807                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
808                         dlg.set_copyright("Copyright 2008 - LGPL")
809                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice/Grandcentral account.  This application is not affiliated with Google in any way")
810                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
811                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
812                         dlg.run()
813                         dlg.destroy()
814                 except Exception, e:
815                         self._errorDisplay.push_exception()
816
817
818 def run_doctest():
819         import doctest
820
821         failureCount, testCount = doctest.testmod()
822         if not failureCount:
823                 print "Tests Successful"
824                 sys.exit(0)
825         else:
826                 sys.exit(1)
827
828
829 def run_dialpad():
830         _lock_file = os.path.join(constants._data_path_, ".lock")
831         #with gtk_toolbox.flock(_lock_file, 0):
832         gtk.gdk.threads_init()
833
834         if hildonize.IS_HILDON_SUPPORTED:
835                 gtk.set_application_name(constants.__pretty_app_name__)
836         handle = Dialcentral()
837         gtk.main()
838
839
840 class DummyOptions(object):
841
842         def __init__(self):
843                 self.test = False
844
845
846 if __name__ == "__main__":
847         logging.basicConfig(level=logging.DEBUG)
848         try:
849                 if len(sys.argv) > 1:
850                         try:
851                                 import optparse
852                         except ImportError:
853                                 optparse = None
854
855                         if optparse is not None:
856                                 parser = optparse.OptionParser()
857                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
858                                 (commandOptions, commandArgs) = parser.parse_args()
859                 else:
860                         commandOptions = DummyOptions()
861                         commandArgs = []
862
863                 if commandOptions.test:
864                         run_doctest()
865                 else:
866                         run_dialpad()
867         finally:
868                 logging.shutdown()