Removed rotation support and added paste accelerator support to the Dialpad
[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 attempt_login(self, numOfAttempts = 10, force = False):
333                 """
334                 @todo Handle user notification better like attempting to login and failed login
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                                 try:
346                                         self.refresh_session()
347                                         serviceId = self._defaultBackendId
348                                         loggedIn = True
349                                 except Exception, e:
350                                         logging.exception('Session refresh failed with the following message "%s"' % str(e))
351
352                         if not loggedIn:
353                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
354
355                         with gtk_toolbox.gtk_lock():
356                                 self._change_loggedin_status(serviceId)
357                 except Exception, e:
358                         with gtk_toolbox.gtk_lock():
359                                 self._errorDisplay.push_exception()
360
361         def _spawn_attempt_login(self, *args):
362                 self._loginSink.send(args)
363
364         def refresh_session(self):
365                 """
366                 @note Thread agnostic
367                 """
368                 assert self._initDone, "Attempting login before app is fully loaded"
369
370                 loggedIn = False
371                 if not loggedIn:
372                         loggedIn = self._login_by_cookie()
373                 if not loggedIn:
374                         loggedIn = self._login_by_settings()
375
376                 if not loggedIn:
377                         raise RuntimeError("Login Failed")
378
379         def _login_by_cookie(self):
380                 """
381                 @note Thread agnostic
382                 """
383                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
384                 if loggedIn:
385                         logging.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
386                 return loggedIn
387
388         def _login_by_settings(self):
389                 """
390                 @note Thread agnostic
391                 """
392                 username, password = self._credentials
393                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
394                 if loggedIn:
395                         self._credentials = username, password
396                         logging.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
397                 return loggedIn
398
399         def _login_by_user(self, numOfAttempts):
400                 """
401                 @note This must be run outside of the UI lock
402                 """
403                 loggedIn, (username, password) = False, self._credentials
404                 tmpServiceId = self.GV_BACKEND
405                 for attemptCount in xrange(numOfAttempts):
406                         if loggedIn:
407                                 break
408                         with gtk_toolbox.gtk_lock():
409                                 credentials = self._credentialsDialog.request_credentials(
410                                         defaultCredentials = self._credentials
411                                 )
412                                 if not self._phoneBackends[tmpServiceId].get_callback_number():
413                                         # subtle reminder to the users to configure things
414                                         self._notebook.set_current_page(self.ACCOUNT_TAB)
415                         username, password = credentials
416                         loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
417
418                 if loggedIn:
419                         serviceId = tmpServiceId
420                         self._credentials = username, password
421                         logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
422                 else:
423                         serviceId = self.NULL_BACKEND
424
425                 return loggedIn, serviceId
426
427         def _select_action(self, action, number, message):
428                 self.refresh_session()
429                 if action == "select":
430                         self._dialpads[self._selectedBackendId].set_number(number)
431                         self._notebook.set_current_page(self.KEYPAD_TAB)
432                 elif action == "dial":
433                         self._on_dial_clicked(number)
434                 elif action == "sms":
435                         self._on_sms_clicked(number, message)
436                 else:
437                         assert False, "Unknown action: %s" % action
438
439         def _change_loggedin_status(self, newStatus):
440                 oldStatus = self._selectedBackendId
441                 if oldStatus == newStatus:
442                         return
443
444                 self._dialpads[oldStatus].disable()
445                 self._accountViews[oldStatus].disable()
446                 self._recentViews[oldStatus].disable()
447                 self._messagesViews[oldStatus].disable()
448                 self._contactsViews[oldStatus].disable()
449
450                 self._dialpads[newStatus].enable()
451                 self._accountViews[newStatus].enable()
452                 self._recentViews[newStatus].enable()
453                 self._messagesViews[newStatus].enable()
454                 self._contactsViews[newStatus].enable()
455
456                 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
457                         self._phoneBackends[self._selectedBackendId].set_sane_callback()
458                 self._accountViews[self._selectedBackendId].update()
459                 self._refresh_active_tab()
460
461                 self._selectedBackendId = newStatus
462
463         def load_settings(self, config):
464                 """
465                 @note UI Thread
466                 """
467                 try:
468                         self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
469                         blobs = (
470                                 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
471                                 for i in xrange(len(self._credentials))
472                         )
473                         creds = (
474                                 base64.b64decode(blob)
475                                 for blob in blobs
476                         )
477                         self._credentials = tuple(creds)
478
479                         if self._alarmHandler is not None:
480                                 self._alarmHandler.load_settings(config, "alarm")
481                 except ConfigParser.NoOptionError, e:
482                         logging.exception(
483                                 "Settings file %s is missing section %s" % (
484                                         constants._user_settings_,
485                                         e.section,
486                                 ),
487                         )
488                 except ConfigParser.NoSectionError, e:
489                         logging.exception(
490                                 "Settings file %s is missing section %s" % (
491                                         constants._user_settings_,
492                                         e.section,
493                                 ),
494                         )
495
496                 for backendId, view in itertools.chain(
497                         self._dialpads.iteritems(),
498                         self._accountViews.iteritems(),
499                         self._messagesViews.iteritems(),
500                         self._recentViews.iteritems(),
501                         self._contactsViews.iteritems(),
502                 ):
503                         sectionName = "%s - %s" % (backendId, view.name())
504                         try:
505                                 view.load_settings(config, sectionName)
506                         except ConfigParser.NoOptionError, e:
507                                 logging.exception(
508                                         "Settings file %s is missing section %s" % (
509                                                 constants._user_settings_,
510                                                 e.section,
511                                         ),
512                                 )
513                         except ConfigParser.NoSectionError, e:
514                                 logging.exception(
515                                         "Settings file %s is missing section %s" % (
516                                                 constants._user_settings_,
517                                                 e.section,
518                                         ),
519                                 )
520
521                 try:
522                         previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
523                         if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
524                                 hildonize.window_to_landscape(self._window)
525                         elif previousOrientation == gtk.ORIENTATION_VERTICAL:
526                                 hildonize.window_to_portrait(self._window)
527                 except ConfigParser.NoOptionError, e:
528                         logging.exception(
529                                 "Settings file %s is missing section %s" % (
530                                         constants._user_settings_,
531                                         e.section,
532                                 ),
533                         )
534                 except ConfigParser.NoSectionError, e:
535                         logging.exception(
536                                 "Settings file %s is missing section %s" % (
537                                         constants._user_settings_,
538                                         e.section,
539                                 ),
540                         )
541
542         def save_settings(self, config):
543                 """
544                 @note Thread Agnostic
545                 """
546                 config.add_section(constants.__pretty_app_name__)
547                 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
548                 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
549                 for i, value in enumerate(self._credentials):
550                         blob = base64.b64encode(value)
551                         config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
552                 config.add_section("alarm")
553                 if self._alarmHandler is not None:
554                         self._alarmHandler.save_settings(config, "alarm")
555
556                 for backendId, view in itertools.chain(
557                         self._dialpads.iteritems(),
558                         self._accountViews.iteritems(),
559                         self._messagesViews.iteritems(),
560                         self._recentViews.iteritems(),
561                         self._contactsViews.iteritems(),
562                 ):
563                         sectionName = "%s - %s" % (backendId, view.name())
564                         config.add_section(sectionName)
565                         view.save_settings(config, sectionName)
566
567         def _save_settings(self):
568                 """
569                 @note Thread Agnostic
570                 """
571                 config = ConfigParser.SafeConfigParser()
572                 self.save_settings(config)
573                 with open(constants._user_settings_, "wb") as configFile:
574                         config.write(configFile)
575
576         def _refresh_active_tab(self):
577                 pageIndex = self._notebook.get_current_page()
578                 if pageIndex == self.CONTACTS_TAB:
579                         self._contactsViews[self._selectedBackendId].update(force=True)
580                 elif pageIndex == self.RECENT_TAB:
581                         self._recentViews[self._selectedBackendId].update(force=True)
582                 elif pageIndex == self.MESSAGES_TAB:
583                         self._messagesViews[self._selectedBackendId].update(force=True)
584
585                 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
586                         if self._ledHandler is not None:
587                                 self._ledHandler.off()
588
589         def _on_close(self, *args, **kwds):
590                 try:
591                         if self._osso is not None:
592                                 self._osso.close()
593
594                         if self._initDone:
595                                 self._save_settings()
596                 finally:
597                         gtk.main_quit()
598
599         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
600                 """
601                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
602                 For system_inactivity, we have no background tasks to pause
603
604                 @note Hildon specific
605                 """
606                 try:
607                         if memory_low:
608                                 for backendId in self.BACKENDS:
609                                         self._phoneBackends[backendId].clear_caches()
610                                 self._contactsViews[self._selectedBackendId].clear_caches()
611                                 gc.collect()
612
613                         if save_unsaved_data or shutdown:
614                                 self._save_settings()
615                 except Exception, e:
616                         self._errorDisplay.push_exception()
617
618         def _on_connection_change(self, connection, event, magicIdentifier):
619                 """
620                 @note Hildon specific
621                 """
622                 try:
623                         import conic
624
625                         status = event.get_status()
626                         error = event.get_error()
627                         iap_id = event.get_iap_id()
628                         bearer = event.get_bearer_type()
629
630                         if status == conic.STATUS_CONNECTED:
631                                 if self._initDone:
632                                         self._spawn_attempt_login(2)
633                         elif status == conic.STATUS_DISCONNECTED:
634                                 if self._initDone:
635                                         self._defaultBackendId = self._selectedBackendId
636                                         self._change_loggedin_status(self.NULL_BACKEND)
637                 except Exception, e:
638                         self._errorDisplay.push_exception()
639
640         def _on_window_state_change(self, widget, event, *args):
641                 """
642                 @note Hildon specific
643                 """
644                 try:
645                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
646                                 self._isFullScreen = True
647                         else:
648                                 self._isFullScreen = False
649                 except Exception, e:
650                         self._errorDisplay.push_exception()
651
652         def _on_key_press(self, widget, event, *args):
653                 """
654                 @note Hildon specific
655                 """
656                 try:
657                         if event.keyval == gtk.keysyms.F6:
658                                 if self._isFullScreen:
659                                         self._window.unfullscreen()
660                                 else:
661                                         self._window.fullscreen()
662                 except Exception, e:
663                         self._errorDisplay.push_exception()
664
665         def _on_clearcookies_clicked(self, *args):
666                 try:
667                         self._phoneBackends[self._selectedBackendId].logout()
668                         self._accountViews[self._selectedBackendId].clear()
669                         self._recentViews[self._selectedBackendId].clear()
670                         self._messagesViews[self._selectedBackendId].clear()
671                         self._contactsViews[self._selectedBackendId].clear()
672                         self._change_loggedin_status(self.NULL_BACKEND)
673
674                         self._spawn_attempt_login(2, True)
675                 except Exception, e:
676                         self._errorDisplay.push_exception()
677
678         def _on_notebook_switch_page(self, notebook, page, pageIndex):
679                 try:
680                         self._reset_tab_refresh()
681
682                         didRecentUpdate = False
683                         didMessagesUpdate = False
684
685                         if pageIndex == self.RECENT_TAB:
686                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
687                         elif pageIndex == self.MESSAGES_TAB:
688                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
689                         elif pageIndex == self.CONTACTS_TAB:
690                                 self._contactsViews[self._selectedBackendId].update()
691                         elif pageIndex == self.ACCOUNT_TAB:
692                                 self._accountViews[self._selectedBackendId].update()
693
694                         if didRecentUpdate or didMessagesUpdate:
695                                 if self._ledHandler is not None:
696                                         self._ledHandler.off()
697                 except Exception, e:
698                         self._errorDisplay.push_exception()
699
700         def _set_tab_refresh(self, *args):
701                 try:
702                         pageIndex = self._notebook.get_current_page()
703                         child = self._notebook.get_nth_page(pageIndex)
704                         self._notebook.get_tab_label(child).set_text("Refresh?")
705                 except Exception, e:
706                         self._errorDisplay.push_exception()
707                 return False
708
709         def _reset_tab_refresh(self, *args):
710                 try:
711                         pageIndex = self._notebook.get_current_page()
712                         child = self._notebook.get_nth_page(pageIndex)
713                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
714                 except Exception, e:
715                         self._errorDisplay.push_exception()
716                 return False
717
718         def _on_tab_refresh(self, *args):
719                 try:
720                         self._refresh_active_tab()
721                         self._reset_tab_refresh()
722                 except Exception, e:
723                         self._errorDisplay.push_exception()
724                 return False
725
726         def _on_sms_clicked(self, number, message):
727                 try:
728                         assert number, "No number specified"
729                         assert message, "Empty message"
730                         try:
731                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
732                         except Exception, e:
733                                 loggedIn = False
734                                 self._errorDisplay.push_exception()
735                                 return
736
737                         if not loggedIn:
738                                 self._errorDisplay.push_message(
739                                         "Backend link with grandcentral is not working, please try again"
740                                 )
741                                 return
742
743                         dialed = False
744                         try:
745                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
746                                 dialed = True
747                         except Exception, e:
748                                 self._errorDisplay.push_exception()
749
750                         if dialed:
751                                 self._dialpads[self._selectedBackendId].clear()
752                 except Exception, e:
753                         self._errorDisplay.push_exception()
754
755         def _on_dial_clicked(self, number):
756                 try:
757                         assert number, "No number to call"
758                         try:
759                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
760                         except Exception, e:
761                                 loggedIn = False
762                                 self._errorDisplay.push_exception()
763                                 return
764
765                         if not loggedIn:
766                                 self._errorDisplay.push_message(
767                                         "Backend link with grandcentral is not working, please try again"
768                                 )
769                                 return
770
771                         dialed = False
772                         try:
773                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
774                                 self._phoneBackends[self._selectedBackendId].dial(number)
775                                 dialed = True
776                         except Exception, e:
777                                 self._errorDisplay.push_exception()
778
779                         if dialed:
780                                 self._dialpads[self._selectedBackendId].clear()
781                 except Exception, e:
782                         self._errorDisplay.push_exception()
783
784         def _on_menu_refresh(self, *args):
785                 try:
786                         self._refresh_active_tab()
787                 except Exception, e:
788                         self._errorDisplay.push_exception()
789
790         def _on_paste(self, *args):
791                 try:
792                         contents = self._clipboard.wait_for_text()
793                         if contents is not None:
794                                 self._dialpads[self._selectedBackendId].set_number(contents)
795                 except Exception, e:
796                         self._errorDisplay.push_exception()
797
798         def _on_about_activate(self, *args):
799                 try:
800                         dlg = gtk.AboutDialog()
801                         dlg.set_name(constants.__pretty_app_name__)
802                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
803                         dlg.set_copyright("Copyright 2008 - LGPL")
804                         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")
805                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
806                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
807                         dlg.run()
808                         dlg.destroy()
809                 except Exception, e:
810                         self._errorDisplay.push_exception()
811
812
813 def run_doctest():
814         import doctest
815
816         failureCount, testCount = doctest.testmod()
817         if not failureCount:
818                 print "Tests Successful"
819                 sys.exit(0)
820         else:
821                 sys.exit(1)
822
823
824 def run_dialpad():
825         _lock_file = os.path.join(constants._data_path_, ".lock")
826         #with gtk_toolbox.flock(_lock_file, 0):
827         gtk.gdk.threads_init()
828
829         if hildonize.IS_HILDON_SUPPORTED:
830                 gtk.set_application_name(constants.__pretty_app_name__)
831         handle = Dialcentral()
832         gtk.main()
833
834
835 class DummyOptions(object):
836
837         def __init__(self):
838                 self.test = False
839
840
841 if __name__ == "__main__":
842         logging.basicConfig(level=logging.DEBUG)
843         try:
844                 if len(sys.argv) > 1:
845                         try:
846                                 import optparse
847                         except ImportError:
848                                 optparse = None
849
850                         if optparse is not None:
851                                 parser = optparse.OptionParser()
852                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
853                                 (commandOptions, commandArgs) = parser.parse_args()
854                 else:
855                         commandOptions = DummyOptions()
856                         commandArgs = []
857
858                 if commandOptions.test:
859                         run_doctest()
860                 else:
861                         run_dialpad()
862         finally:
863                 logging.shutdown()