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))
137 hildonize.set_button_thumb_selectable(button)
139 replacementButtons = [gtk.Button("Test")]
140 menu = hildonize.hildonize_menu(
142 self._widgetTree.get_widget("dialpad_menubar"),
146 self._window.connect("key-press-event", self._on_key_press)
147 self._window.connect("window-state-event", self._on_window_state_change)
148 if not hildonize.IS_HILDON_SUPPORTED:
149 _moduleLogger.warning("No hildonization support")
151 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
153 self._window.connect("destroy", self._on_close)
154 self._window.set_default_size(800, 300)
155 self._window.show_all()
157 self._loginSink = gtk_toolbox.threaded_stage(
160 gtk_toolbox.null_sink(),
164 backgroundSetup = threading.Thread(target=self._idle_setup)
165 backgroundSetup.setDaemon(True)
166 backgroundSetup.start()
168 def _idle_setup(self):
170 If something can be done after the UI loads, push it here so it's not blocking the UI
172 # Barebones UI handlers
177 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
178 with gtk_toolbox.gtk_lock():
179 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
180 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
181 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
182 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
183 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
185 self._dialpads[self._selectedBackendId].enable()
186 self._accountViews[self._selectedBackendId].enable()
187 self._recentViews[self._selectedBackendId].enable()
188 self._messagesViews[self._selectedBackendId].enable()
189 self._contactsViews[self._selectedBackendId].enable()
191 with gtk_toolbox.gtk_lock():
192 self._errorDisplay.push_exception()
194 # Setup maemo specifics
198 except (ImportError, OSError):
202 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
203 device = osso.DeviceState(self._osso)
204 device.set_device_state_callback(self._on_device_state_change, 0)
206 _moduleLogger.warning("No device state support")
210 self._alarmHandler = alarm_handler.AlarmHandler()
211 except (ImportError, OSError):
214 with gtk_toolbox.gtk_lock():
215 self._errorDisplay.push_exception()
217 _moduleLogger.warning("No notification support")
218 if hildonize.IS_HILDON_SUPPORTED:
221 self._ledHandler = led_handler.LedHandler()
223 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
224 self._ledHandler = None
226 self._ledHandler = None
230 except (ImportError, OSError):
232 self._connection = None
233 if conic is not None:
234 self._connection = conic.Connection()
235 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
236 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
238 _moduleLogger.warning("No connection support")
240 with gtk_toolbox.gtk_lock():
241 self._errorDisplay.push_exception()
243 # Setup costly backends
250 os.makedirs(constants._data_path_)
254 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
256 self._phoneBackends.update({
257 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
259 with gtk_toolbox.gtk_lock():
260 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
261 self._dialpads.update({
262 self.GV_BACKEND: unifiedDialpad,
264 self._accountViews.update({
265 self.GV_BACKEND: gv_views.AccountInfo(
266 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
269 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
270 self._recentViews.update({
271 self.GV_BACKEND: gv_views.RecentCallsView(
272 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
275 self._messagesViews.update({
276 self.GV_BACKEND: gv_views.MessagesView(
277 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
280 self._contactsViews.update({
281 self.GV_BACKEND: gv_views.ContactsView(
282 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
286 fsContactsPath = os.path.join(constants._data_path_, "contacts")
287 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
289 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
290 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
291 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
292 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
295 self._phoneBackends[self.GV_BACKEND],
298 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
299 self._contactsViews[self.GV_BACKEND].append(mergedBook)
300 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
301 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
304 "on_paste": self._on_paste,
305 "on_refresh": self._on_menu_refresh,
306 "on_clearcookies_clicked": self._on_clearcookies_clicked,
307 "on_about_activate": self._on_about_activate,
309 if hildonize.GTK_MENU_USED:
310 self._widgetTree.signal_autoconnect(callbackMapping)
311 self._notebook.connect("switch-page", self._on_notebook_switch_page)
312 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
314 with gtk_toolbox.gtk_lock():
315 self._originalCurrentLabels = [
316 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
317 for pageIndex in xrange(self._notebook.get_n_pages())
319 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
320 self._notebookTapHandler.enable()
321 self._notebookTapHandler.on_tap = self._reset_tab_refresh
322 self._notebookTapHandler.on_hold = self._on_tab_refresh
323 self._notebookTapHandler.on_holding = self._set_tab_refresh
324 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
326 self._initDone = True
328 config = ConfigParser.SafeConfigParser()
329 config.read(constants._user_settings_)
330 with gtk_toolbox.gtk_lock():
331 self.load_settings(config)
333 with gtk_toolbox.gtk_lock():
334 self._errorDisplay.push_exception()
336 self._spawn_attempt_login(2)
338 def _spawn_attempt_login(self, *args):
339 self._loginSink.send(args)
341 def _attempt_login(self, numOfAttempts = 10, force = False):
343 @note This must be run outside of the UI lock
346 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
347 assert self._initDone, "Attempting login before app is fully loaded"
349 serviceId = self.NULL_BACKEND
352 with gtk_toolbox.gtk_lock():
353 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
355 self.refresh_session()
356 serviceId = self._defaultBackendId
359 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
361 with gtk_toolbox.gtk_lock():
362 hildonize.show_busy_banner_end(banner)
365 loggedIn, serviceId = self._login_by_user(numOfAttempts)
367 with gtk_toolbox.gtk_lock():
368 self._change_loggedin_status(serviceId)
370 hildonize.show_information_banner(self._window, "Logged In")
372 hildonize.show_information_banner(self._window, "Login Failed")
374 with gtk_toolbox.gtk_lock():
375 self._errorDisplay.push_exception()
377 def refresh_session(self):
379 @note Thread agnostic
381 assert self._initDone, "Attempting login before app is fully loaded"
385 loggedIn = self._login_by_cookie()
387 loggedIn = self._login_by_settings()
390 raise RuntimeError("Login Failed")
392 def _login_by_cookie(self):
394 @note Thread agnostic
396 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
398 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
401 def _login_by_settings(self):
403 @note Thread agnostic
405 username, password = self._credentials
406 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
408 self._credentials = username, password
409 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
412 def _login_by_user(self, numOfAttempts):
414 @note This must be run outside of the UI lock
416 loggedIn, (username, password) = False, self._credentials
417 tmpServiceId = self.GV_BACKEND
418 for attemptCount in xrange(numOfAttempts):
421 with gtk_toolbox.gtk_lock():
422 credentials = self._credentialsDialog.request_credentials(
423 defaultCredentials = self._credentials
425 if not self._phoneBackends[tmpServiceId].get_callback_number():
426 # subtle reminder to the users to configure things
427 self._notebook.set_current_page(self.ACCOUNT_TAB)
428 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
430 username, password = credentials
431 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
433 with gtk_toolbox.gtk_lock():
434 hildonize.show_busy_banner_end(banner)
437 serviceId = tmpServiceId
438 self._credentials = username, password
439 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
441 serviceId = self.NULL_BACKEND
442 self._notebook.set_current_page(self.ACCOUNT_TAB)
444 return loggedIn, serviceId
446 def _select_action(self, action, number, message):
447 self.refresh_session()
448 if action == "select":
449 self._dialpads[self._selectedBackendId].set_number(number)
450 self._notebook.set_current_page(self.KEYPAD_TAB)
451 elif action == "dial":
452 self._on_dial_clicked(number)
453 elif action == "sms":
454 self._on_sms_clicked(number, message)
456 assert False, "Unknown action: %s" % action
458 def _change_loggedin_status(self, newStatus):
459 oldStatus = self._selectedBackendId
460 if oldStatus == newStatus:
463 self._dialpads[oldStatus].disable()
464 self._accountViews[oldStatus].disable()
465 self._recentViews[oldStatus].disable()
466 self._messagesViews[oldStatus].disable()
467 self._contactsViews[oldStatus].disable()
469 self._dialpads[newStatus].enable()
470 self._accountViews[newStatus].enable()
471 self._recentViews[newStatus].enable()
472 self._messagesViews[newStatus].enable()
473 self._contactsViews[newStatus].enable()
475 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
476 self._phoneBackends[self._selectedBackendId].set_sane_callback()
478 self._selectedBackendId = newStatus
480 self._accountViews[self._selectedBackendId].update()
481 self._refresh_active_tab()
483 def load_settings(self, config):
488 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
490 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
491 for i in xrange(len(self._credentials))
494 base64.b64decode(blob)
497 self._credentials = tuple(creds)
499 if self._alarmHandler is not None:
500 self._alarmHandler.load_settings(config, "alarm")
501 except ConfigParser.NoOptionError, e:
502 _moduleLogger.exception(
503 "Settings file %s is missing section %s" % (
504 constants._user_settings_,
508 except ConfigParser.NoSectionError, e:
509 _moduleLogger.exception(
510 "Settings file %s is missing section %s" % (
511 constants._user_settings_,
516 for backendId, view in itertools.chain(
517 self._dialpads.iteritems(),
518 self._accountViews.iteritems(),
519 self._messagesViews.iteritems(),
520 self._recentViews.iteritems(),
521 self._contactsViews.iteritems(),
523 sectionName = "%s - %s" % (backendId, view.name())
525 view.load_settings(config, sectionName)
526 except ConfigParser.NoOptionError, e:
527 _moduleLogger.exception(
528 "Settings file %s is missing section %s" % (
529 constants._user_settings_,
533 except ConfigParser.NoSectionError, e:
534 _moduleLogger.exception(
535 "Settings file %s is missing section %s" % (
536 constants._user_settings_,
542 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
543 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
544 hildonize.window_to_landscape(self._window)
545 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
546 hildonize.window_to_portrait(self._window)
547 except ConfigParser.NoOptionError, e:
548 _moduleLogger.exception(
549 "Settings file %s is missing section %s" % (
550 constants._user_settings_,
554 except ConfigParser.NoSectionError, e:
555 _moduleLogger.exception(
556 "Settings file %s is missing section %s" % (
557 constants._user_settings_,
562 def save_settings(self, config):
564 @note Thread Agnostic
566 config.add_section(constants.__pretty_app_name__)
567 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
568 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
569 for i, value in enumerate(self._credentials):
570 blob = base64.b64encode(value)
571 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
572 config.add_section("alarm")
573 if self._alarmHandler is not None:
574 self._alarmHandler.save_settings(config, "alarm")
576 for backendId, view in itertools.chain(
577 self._dialpads.iteritems(),
578 self._accountViews.iteritems(),
579 self._messagesViews.iteritems(),
580 self._recentViews.iteritems(),
581 self._contactsViews.iteritems(),
583 sectionName = "%s - %s" % (backendId, view.name())
584 config.add_section(sectionName)
585 view.save_settings(config, sectionName)
587 def _save_settings(self):
589 @note Thread Agnostic
591 config = ConfigParser.SafeConfigParser()
592 self.save_settings(config)
593 with open(constants._user_settings_, "wb") as configFile:
594 config.write(configFile)
596 def _refresh_active_tab(self):
597 pageIndex = self._notebook.get_current_page()
598 if pageIndex == self.CONTACTS_TAB:
599 self._contactsViews[self._selectedBackendId].update(force=True)
600 elif pageIndex == self.RECENT_TAB:
601 self._recentViews[self._selectedBackendId].update(force=True)
602 elif pageIndex == self.MESSAGES_TAB:
603 self._messagesViews[self._selectedBackendId].update(force=True)
605 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
606 if self._ledHandler is not None:
607 self._ledHandler.off()
609 def _on_close(self, *args, **kwds):
611 if self._osso is not None:
615 self._save_settings()
619 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
621 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
622 For system_inactivity, we have no background tasks to pause
624 @note Hildon specific
628 for backendId in self.BACKENDS:
629 self._phoneBackends[backendId].clear_caches()
630 self._contactsViews[self._selectedBackendId].clear_caches()
633 if save_unsaved_data or shutdown:
634 self._save_settings()
636 self._errorDisplay.push_exception()
638 def _on_connection_change(self, connection, event, magicIdentifier):
640 @note Hildon specific
645 status = event.get_status()
646 error = event.get_error()
647 iap_id = event.get_iap_id()
648 bearer = event.get_bearer_type()
650 if status == conic.STATUS_CONNECTED:
652 self._spawn_attempt_login(2)
653 elif status == conic.STATUS_DISCONNECTED:
655 self._defaultBackendId = self._selectedBackendId
656 self._change_loggedin_status(self.NULL_BACKEND)
658 self._errorDisplay.push_exception()
660 def _on_window_state_change(self, widget, event, *args):
662 @note Hildon specific
665 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
666 self._isFullScreen = True
668 self._isFullScreen = False
670 self._errorDisplay.push_exception()
672 def _on_key_press(self, widget, event, *args):
674 @note Hildon specific
678 event.keyval == gtk.keysyms.F6 or
679 event.keyval == gtk.keysyms.Return and event.get_state() & gtk.gdk.CONTROL_MASK
681 if self._isFullScreen:
682 self._window.unfullscreen()
684 self._window.fullscreen()
686 self._errorDisplay.push_exception()
688 def _on_clearcookies_clicked(self, *args):
690 self._phoneBackends[self._selectedBackendId].logout()
691 self._accountViews[self._selectedBackendId].clear()
692 self._recentViews[self._selectedBackendId].clear()
693 self._messagesViews[self._selectedBackendId].clear()
694 self._contactsViews[self._selectedBackendId].clear()
695 self._change_loggedin_status(self.NULL_BACKEND)
697 self._spawn_attempt_login(2, True)
699 self._errorDisplay.push_exception()
701 def _on_notebook_switch_page(self, notebook, page, pageIndex):
703 self._reset_tab_refresh()
705 didRecentUpdate = False
706 didMessagesUpdate = False
708 if pageIndex == self.RECENT_TAB:
709 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
710 elif pageIndex == self.MESSAGES_TAB:
711 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
712 elif pageIndex == self.CONTACTS_TAB:
713 self._contactsViews[self._selectedBackendId].update()
714 elif pageIndex == self.ACCOUNT_TAB:
715 self._accountViews[self._selectedBackendId].update()
717 if didRecentUpdate or didMessagesUpdate:
718 if self._ledHandler is not None:
719 self._ledHandler.off()
721 self._errorDisplay.push_exception()
723 def _set_tab_refresh(self, *args):
725 pageIndex = self._notebook.get_current_page()
726 child = self._notebook.get_nth_page(pageIndex)
727 self._notebook.get_tab_label(child).set_text("Refresh?")
729 self._errorDisplay.push_exception()
732 def _reset_tab_refresh(self, *args):
734 pageIndex = self._notebook.get_current_page()
735 child = self._notebook.get_nth_page(pageIndex)
736 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
738 self._errorDisplay.push_exception()
741 def _on_tab_refresh(self, *args):
743 self._refresh_active_tab()
744 self._reset_tab_refresh()
746 self._errorDisplay.push_exception()
749 def _on_sms_clicked(self, number, message):
751 assert number, "No number specified"
752 assert message, "Empty message"
754 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
757 self._errorDisplay.push_exception()
761 self._errorDisplay.push_message(
762 "Backend link with GoogleVoice is not working, please try again"
768 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
769 hildonize.show_information_banner(self._window, "Sending to %s" % number)
772 self._errorDisplay.push_exception()
775 self._dialpads[self._selectedBackendId].clear()
777 self._errorDisplay.push_exception()
779 def _on_dial_clicked(self, number):
781 assert number, "No number to call"
783 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
786 self._errorDisplay.push_exception()
790 self._errorDisplay.push_message(
791 "Backend link with GoogleVoice is not working, please try again"
797 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
798 self._phoneBackends[self._selectedBackendId].dial(number)
799 hildonize.show_information_banner(self._window, "Calling %s" % number)
802 self._errorDisplay.push_exception()
805 self._dialpads[self._selectedBackendId].clear()
807 self._errorDisplay.push_exception()
809 def _on_menu_refresh(self, *args):
811 self._refresh_active_tab()
813 self._errorDisplay.push_exception()
815 def _on_paste(self, *args):
817 contents = self._clipboard.wait_for_text()
818 if contents is not None:
819 self._dialpads[self._selectedBackendId].set_number(contents)
821 self._errorDisplay.push_exception()
823 def _on_about_activate(self, *args):
825 dlg = gtk.AboutDialog()
826 dlg.set_name(constants.__pretty_app_name__)
827 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
828 dlg.set_copyright("Copyright 2008 - LGPL")
829 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
830 dlg.set_website("http://gc-dialer.garage.maemo.org/")
831 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
835 self._errorDisplay.push_exception()
841 failureCount, testCount = doctest.testmod()
843 print "Tests Successful"
850 _lock_file = os.path.join(constants._data_path_, ".lock")
851 #with gtk_toolbox.flock(_lock_file, 0):
852 gtk.gdk.threads_init()
854 if hildonize.IS_HILDON_SUPPORTED:
855 gtk.set_application_name(constants.__pretty_app_name__)
856 handle = Dialcentral()
860 class DummyOptions(object):
866 if __name__ == "__main__":
867 logging.basicConfig(level=logging.DEBUG)
869 if len(sys.argv) > 1:
875 if optparse is not None:
876 parser = optparse.OptionParser()
877 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
878 (commandOptions, commandArgs) = parser.parse_args()
880 commandOptions = DummyOptions()
883 if commandOptions.test: