4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
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.
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.
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
23 from __future__ import with_statement
42 _moduleLogger = logging.getLogger("dc_glade")
45 def getmtime_nothrow(path):
47 return os.path.getmtime(path)
52 def display_error_message(msg):
53 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
55 def close(dialog, response):
57 error_dialog.connect("response", close)
61 class Dialcentral(object):
64 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
65 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
66 '/usr/lib/dialcentral/dialcentral.glade',
77 BACKENDS = (NULL_BACKEND, GV_BACKEND)
80 self._initDone = False
81 self._connection = None
83 self._clipboard = gtk.clipboard_get()
85 self._credentials = ("", "")
86 self._selectedBackendId = self.NULL_BACKEND
87 self._defaultBackendId = self.GV_BACKEND
88 self._phoneBackends = None
90 self._accountViews = None
91 self._messagesViews = None
92 self._recentViews = None
93 self._contactsViews = None
94 self._alarmHandler = None
95 self._ledHandler = None
96 self._originalCurrentLabels = []
98 for path in self._glade_files:
99 if os.path.isfile(path):
100 self._widgetTree = gtk.glade.XML(path)
103 display_error_message("Cannot find dialcentral.glade")
107 self._window = self._widgetTree.get_widget("mainWindow")
108 self._notebook = self._widgetTree.get_widget("notebook")
109 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
110 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
112 self._isFullScreen = False
113 self._app = hildonize.get_app_class()()
114 self._window = hildonize.hildonize_window(self._app, self._window)
115 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
116 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
118 for scrollingWidget in (
119 'recent_scrolledwindow',
120 'message_scrolledwindow',
121 'contacts_scrolledwindow',
122 "phoneSelectionMessages_scrolledwindow",
123 "smsMessages_scrolledwindow",
125 hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
126 for scrollingWidget in (
127 "phonetypes_scrolledwindow",
128 "smsMessage_scrolledEntry",
130 hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
132 replacementButtons = [gtk.Button("Test")]
133 menu = hildonize.hildonize_menu(
135 self._widgetTree.get_widget("dialpad_menubar"),
139 self._window.connect("key-press-event", self._on_key_press)
140 self._window.connect("window-state-event", self._on_window_state_change)
141 if not hildonize.IS_HILDON_SUPPORTED:
142 _moduleLogger.warning("No hildonization support")
144 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
146 self._window.connect("destroy", self._on_close)
147 self._window.set_default_size(800, 300)
148 self._window.show_all()
150 self._loginSink = gtk_toolbox.threaded_stage(
153 gtk_toolbox.null_sink(),
157 backgroundSetup = threading.Thread(target=self._idle_setup)
158 backgroundSetup.setDaemon(True)
159 backgroundSetup.start()
161 def _idle_setup(self):
163 If something can be done after the UI loads, push it here so it's not blocking the UI
165 # Barebones UI handlers
170 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
171 with gtk_toolbox.gtk_lock():
172 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
173 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
174 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
175 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
176 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
178 self._dialpads[self._selectedBackendId].enable()
179 self._accountViews[self._selectedBackendId].enable()
180 self._recentViews[self._selectedBackendId].enable()
181 self._messagesViews[self._selectedBackendId].enable()
182 self._contactsViews[self._selectedBackendId].enable()
184 with gtk_toolbox.gtk_lock():
185 self._errorDisplay.push_exception()
187 # Setup maemo specifics
191 except (ImportError, OSError):
195 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
196 device = osso.DeviceState(self._osso)
197 device.set_device_state_callback(self._on_device_state_change, 0)
199 _moduleLogger.warning("No device state support")
203 self._alarmHandler = alarm_handler.AlarmHandler()
204 except (ImportError, OSError):
207 with gtk_toolbox.gtk_lock():
208 self._errorDisplay.push_exception()
210 _moduleLogger.warning("No notification support")
211 if hildonize.IS_HILDON_SUPPORTED:
214 self._ledHandler = led_handler.LedHandler()
216 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
217 self._ledHandler = None
219 self._ledHandler = None
223 except (ImportError, OSError):
225 self._connection = None
226 if conic is not None:
227 self._connection = conic.Connection()
228 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
229 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
231 _moduleLogger.warning("No connection support")
233 with gtk_toolbox.gtk_lock():
234 self._errorDisplay.push_exception()
236 # Setup costly backends
243 os.makedirs(constants._data_path_)
247 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
249 self._phoneBackends.update({
250 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
252 with gtk_toolbox.gtk_lock():
253 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
254 self._dialpads.update({
255 self.GV_BACKEND: unifiedDialpad,
257 self._accountViews.update({
258 self.GV_BACKEND: gv_views.AccountInfo(
259 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
262 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
263 self._recentViews.update({
264 self.GV_BACKEND: gv_views.RecentCallsView(
265 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
268 self._messagesViews.update({
269 self.GV_BACKEND: gv_views.MessagesView(
270 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
273 self._contactsViews.update({
274 self.GV_BACKEND: gv_views.ContactsView(
275 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
279 fsContactsPath = os.path.join(constants._data_path_, "contacts")
280 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
282 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
283 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
284 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
285 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
288 self._phoneBackends[self.GV_BACKEND],
291 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
292 self._contactsViews[self.GV_BACKEND].append(mergedBook)
293 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
294 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
297 "on_paste": self._on_paste,
298 "on_refresh": self._on_menu_refresh,
299 "on_clearcookies_clicked": self._on_clearcookies_clicked,
300 "on_about_activate": self._on_about_activate,
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)
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())
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
319 self._initDone = True
321 config = ConfigParser.SafeConfigParser()
322 config.read(constants._user_settings_)
323 with gtk_toolbox.gtk_lock():
324 self.load_settings(config)
326 with gtk_toolbox.gtk_lock():
327 self._errorDisplay.push_exception()
329 self._spawn_attempt_login(2)
331 def _spawn_attempt_login(self, *args):
332 self._loginSink.send(args)
334 def _attempt_login(self, numOfAttempts = 10, force = False):
336 @note This must be run outside of the UI lock
339 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
340 assert self._initDone, "Attempting login before app is fully loaded"
342 serviceId = self.NULL_BACKEND
345 with gtk_toolbox.gtk_lock():
346 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
348 self.refresh_session()
349 serviceId = self._defaultBackendId
352 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
354 with gtk_toolbox.gtk_lock():
355 hildonize.show_busy_banner_end(banner)
358 loggedIn, serviceId = self._login_by_user(numOfAttempts)
360 with gtk_toolbox.gtk_lock():
361 self._change_loggedin_status(serviceId)
363 hildonize.show_information_banner(self._window, "Logged In")
365 hildonize.show_information_banner(self._window, "Login Failed")
367 with gtk_toolbox.gtk_lock():
368 self._errorDisplay.push_exception()
370 def refresh_session(self):
372 @note Thread agnostic
374 assert self._initDone, "Attempting login before app is fully loaded"
378 loggedIn = self._login_by_cookie()
380 loggedIn = self._login_by_settings()
383 raise RuntimeError("Login Failed")
385 def _login_by_cookie(self):
387 @note Thread agnostic
389 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
391 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
394 def _login_by_settings(self):
396 @note Thread agnostic
398 username, password = self._credentials
399 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
401 self._credentials = username, password
402 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
405 def _login_by_user(self, numOfAttempts):
407 @note This must be run outside of the UI lock
409 loggedIn, (username, password) = False, self._credentials
410 tmpServiceId = self.GV_BACKEND
411 for attemptCount in xrange(numOfAttempts):
414 with gtk_toolbox.gtk_lock():
415 credentials = self._credentialsDialog.request_credentials(
416 defaultCredentials = self._credentials
418 if not self._phoneBackends[tmpServiceId].get_callback_number():
419 # subtle reminder to the users to configure things
420 self._notebook.set_current_page(self.ACCOUNT_TAB)
421 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
423 username, password = credentials
424 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
426 with gtk_toolbox.gtk_lock():
427 hildonize.show_busy_banner_end(banner)
430 serviceId = tmpServiceId
431 self._credentials = username, password
432 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
434 serviceId = self.NULL_BACKEND
435 self._notebook.set_current_page(self.ACCOUNT_TAB)
437 return loggedIn, serviceId
439 def _select_action(self, action, number, message):
440 self.refresh_session()
441 if action == "select":
442 self._dialpads[self._selectedBackendId].set_number(number)
443 self._notebook.set_current_page(self.KEYPAD_TAB)
444 elif action == "dial":
445 self._on_dial_clicked(number)
446 elif action == "sms":
447 self._on_sms_clicked(number, message)
449 assert False, "Unknown action: %s" % action
451 def _change_loggedin_status(self, newStatus):
452 oldStatus = self._selectedBackendId
453 if oldStatus == newStatus:
456 self._dialpads[oldStatus].disable()
457 self._accountViews[oldStatus].disable()
458 self._recentViews[oldStatus].disable()
459 self._messagesViews[oldStatus].disable()
460 self._contactsViews[oldStatus].disable()
462 self._dialpads[newStatus].enable()
463 self._accountViews[newStatus].enable()
464 self._recentViews[newStatus].enable()
465 self._messagesViews[newStatus].enable()
466 self._contactsViews[newStatus].enable()
468 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
469 self._phoneBackends[self._selectedBackendId].set_sane_callback()
471 self._selectedBackendId = newStatus
473 self._accountViews[self._selectedBackendId].update()
474 self._refresh_active_tab()
476 def load_settings(self, config):
481 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
483 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
484 for i in xrange(len(self._credentials))
487 base64.b64decode(blob)
490 self._credentials = tuple(creds)
492 if self._alarmHandler is not None:
493 self._alarmHandler.load_settings(config, "alarm")
494 except ConfigParser.NoOptionError, e:
495 _moduleLogger.exception(
496 "Settings file %s is missing section %s" % (
497 constants._user_settings_,
501 except ConfigParser.NoSectionError, e:
502 _moduleLogger.exception(
503 "Settings file %s is missing section %s" % (
504 constants._user_settings_,
509 for backendId, view in itertools.chain(
510 self._dialpads.iteritems(),
511 self._accountViews.iteritems(),
512 self._messagesViews.iteritems(),
513 self._recentViews.iteritems(),
514 self._contactsViews.iteritems(),
516 sectionName = "%s - %s" % (backendId, view.name())
518 view.load_settings(config, sectionName)
519 except ConfigParser.NoOptionError, e:
520 _moduleLogger.exception(
521 "Settings file %s is missing section %s" % (
522 constants._user_settings_,
526 except ConfigParser.NoSectionError, e:
527 _moduleLogger.exception(
528 "Settings file %s is missing section %s" % (
529 constants._user_settings_,
535 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
536 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
537 hildonize.window_to_landscape(self._window)
538 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
539 hildonize.window_to_portrait(self._window)
540 except ConfigParser.NoOptionError, e:
541 _moduleLogger.exception(
542 "Settings file %s is missing section %s" % (
543 constants._user_settings_,
547 except ConfigParser.NoSectionError, e:
548 _moduleLogger.exception(
549 "Settings file %s is missing section %s" % (
550 constants._user_settings_,
555 def save_settings(self, config):
557 @note Thread Agnostic
559 config.add_section(constants.__pretty_app_name__)
560 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
561 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
562 for i, value in enumerate(self._credentials):
563 blob = base64.b64encode(value)
564 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
565 config.add_section("alarm")
566 if self._alarmHandler is not None:
567 self._alarmHandler.save_settings(config, "alarm")
569 for backendId, view in itertools.chain(
570 self._dialpads.iteritems(),
571 self._accountViews.iteritems(),
572 self._messagesViews.iteritems(),
573 self._recentViews.iteritems(),
574 self._contactsViews.iteritems(),
576 sectionName = "%s - %s" % (backendId, view.name())
577 config.add_section(sectionName)
578 view.save_settings(config, sectionName)
580 def _save_settings(self):
582 @note Thread Agnostic
584 config = ConfigParser.SafeConfigParser()
585 self.save_settings(config)
586 with open(constants._user_settings_, "wb") as configFile:
587 config.write(configFile)
589 def _refresh_active_tab(self):
590 pageIndex = self._notebook.get_current_page()
591 if pageIndex == self.CONTACTS_TAB:
592 self._contactsViews[self._selectedBackendId].update(force=True)
593 elif pageIndex == self.RECENT_TAB:
594 self._recentViews[self._selectedBackendId].update(force=True)
595 elif pageIndex == self.MESSAGES_TAB:
596 self._messagesViews[self._selectedBackendId].update(force=True)
598 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
599 if self._ledHandler is not None:
600 self._ledHandler.off()
602 def _on_close(self, *args, **kwds):
604 if self._osso is not None:
608 self._save_settings()
612 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
614 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
615 For system_inactivity, we have no background tasks to pause
617 @note Hildon specific
621 for backendId in self.BACKENDS:
622 self._phoneBackends[backendId].clear_caches()
623 self._contactsViews[self._selectedBackendId].clear_caches()
626 if save_unsaved_data or shutdown:
627 self._save_settings()
629 self._errorDisplay.push_exception()
631 def _on_connection_change(self, connection, event, magicIdentifier):
633 @note Hildon specific
638 status = event.get_status()
639 error = event.get_error()
640 iap_id = event.get_iap_id()
641 bearer = event.get_bearer_type()
643 if status == conic.STATUS_CONNECTED:
645 self._spawn_attempt_login(2)
646 elif status == conic.STATUS_DISCONNECTED:
648 self._defaultBackendId = self._selectedBackendId
649 self._change_loggedin_status(self.NULL_BACKEND)
651 self._errorDisplay.push_exception()
653 def _on_window_state_change(self, widget, event, *args):
655 @note Hildon specific
658 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
659 self._isFullScreen = True
661 self._isFullScreen = False
663 self._errorDisplay.push_exception()
665 def _on_key_press(self, widget, event, *args):
667 @note Hildon specific
671 event.keyval == gtk.keysyms.F6 or
672 event.keyval == gtk.keysyms.Return and event.get_state() & gtk.gdk.CONTROL_MASK
674 if self._isFullScreen:
675 self._window.unfullscreen()
677 self._window.fullscreen()
679 self._errorDisplay.push_exception()
681 def _on_clearcookies_clicked(self, *args):
683 self._phoneBackends[self._selectedBackendId].logout()
684 self._accountViews[self._selectedBackendId].clear()
685 self._recentViews[self._selectedBackendId].clear()
686 self._messagesViews[self._selectedBackendId].clear()
687 self._contactsViews[self._selectedBackendId].clear()
688 self._change_loggedin_status(self.NULL_BACKEND)
690 self._spawn_attempt_login(2, True)
692 self._errorDisplay.push_exception()
694 def _on_notebook_switch_page(self, notebook, page, pageIndex):
696 self._reset_tab_refresh()
698 didRecentUpdate = False
699 didMessagesUpdate = False
701 if pageIndex == self.RECENT_TAB:
702 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
703 elif pageIndex == self.MESSAGES_TAB:
704 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
705 elif pageIndex == self.CONTACTS_TAB:
706 self._contactsViews[self._selectedBackendId].update()
707 elif pageIndex == self.ACCOUNT_TAB:
708 self._accountViews[self._selectedBackendId].update()
710 if didRecentUpdate or didMessagesUpdate:
711 if self._ledHandler is not None:
712 self._ledHandler.off()
714 self._errorDisplay.push_exception()
716 def _set_tab_refresh(self, *args):
718 pageIndex = self._notebook.get_current_page()
719 child = self._notebook.get_nth_page(pageIndex)
720 self._notebook.get_tab_label(child).set_text("Refresh?")
722 self._errorDisplay.push_exception()
725 def _reset_tab_refresh(self, *args):
727 pageIndex = self._notebook.get_current_page()
728 child = self._notebook.get_nth_page(pageIndex)
729 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
731 self._errorDisplay.push_exception()
734 def _on_tab_refresh(self, *args):
736 self._refresh_active_tab()
737 self._reset_tab_refresh()
739 self._errorDisplay.push_exception()
742 def _on_sms_clicked(self, number, message):
744 assert number, "No number specified"
745 assert message, "Empty message"
747 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
750 self._errorDisplay.push_exception()
754 self._errorDisplay.push_message(
755 "Backend link with GoogleVoice is not working, please try again"
761 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
762 hildonize.show_information_banner(self._window, "Sending to %s" % number)
765 self._errorDisplay.push_exception()
768 self._dialpads[self._selectedBackendId].clear()
770 self._errorDisplay.push_exception()
772 def _on_dial_clicked(self, number):
774 assert number, "No number to call"
776 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
779 self._errorDisplay.push_exception()
783 self._errorDisplay.push_message(
784 "Backend link with GoogleVoice is not working, please try again"
790 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
791 self._phoneBackends[self._selectedBackendId].dial(number)
792 hildonize.show_information_banner(self._window, "Calling %s" % number)
795 self._errorDisplay.push_exception()
798 self._dialpads[self._selectedBackendId].clear()
800 self._errorDisplay.push_exception()
802 def _on_menu_refresh(self, *args):
804 self._refresh_active_tab()
806 self._errorDisplay.push_exception()
808 def _on_paste(self, *args):
810 contents = self._clipboard.wait_for_text()
811 if contents is not None:
812 self._dialpads[self._selectedBackendId].set_number(contents)
814 self._errorDisplay.push_exception()
816 def _on_about_activate(self, *args):
818 dlg = gtk.AboutDialog()
819 dlg.set_name(constants.__pretty_app_name__)
820 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
821 dlg.set_copyright("Copyright 2008 - LGPL")
822 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
823 dlg.set_website("http://gc-dialer.garage.maemo.org/")
824 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
828 self._errorDisplay.push_exception()
834 failureCount, testCount = doctest.testmod()
836 print "Tests Successful"
843 _lock_file = os.path.join(constants._data_path_, ".lock")
844 #with gtk_toolbox.flock(_lock_file, 0):
845 gtk.gdk.threads_init()
847 if hildonize.IS_HILDON_SUPPORTED:
848 gtk.set_application_name(constants.__pretty_app_name__)
849 handle = Dialcentral()
853 class DummyOptions(object):
859 if __name__ == "__main__":
860 logging.basicConfig(level=logging.DEBUG)
862 if len(sys.argv) > 1:
868 if optparse is not None:
869 parser = optparse.OptionParser()
870 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
871 (commandOptions, commandArgs) = parser.parse_args()
873 commandOptions = DummyOptions()
876 if commandOptions.test: