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