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