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