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))
140 "minutesEntryButton",
143 hildonize.set_button_thumb_selectable(button)
145 replacementButtons = [gtk.Button("Test")]
146 menu = hildonize.hildonize_menu(
148 self._widgetTree.get_widget("dialpad_menubar"),
152 self._window.connect("key-press-event", self._on_key_press)
153 self._window.connect("window-state-event", self._on_window_state_change)
154 if not hildonize.IS_HILDON_SUPPORTED:
155 _moduleLogger.warning("No hildonization support")
157 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
159 self._window.connect("destroy", self._on_close)
160 self._window.set_default_size(800, 300)
161 self._window.show_all()
163 self._loginSink = gtk_toolbox.threaded_stage(
166 gtk_toolbox.null_sink(),
170 backgroundSetup = threading.Thread(target=self._idle_setup)
171 backgroundSetup.setDaemon(True)
172 backgroundSetup.start()
174 def _idle_setup(self):
176 If something can be done after the UI loads, push it here so it's not blocking the UI
178 # Barebones UI handlers
183 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
184 with gtk_toolbox.gtk_lock():
185 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
186 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
187 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
188 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
189 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
191 self._dialpads[self._selectedBackendId].enable()
192 self._accountViews[self._selectedBackendId].enable()
193 self._recentViews[self._selectedBackendId].enable()
194 self._messagesViews[self._selectedBackendId].enable()
195 self._contactsViews[self._selectedBackendId].enable()
197 with gtk_toolbox.gtk_lock():
198 self._errorDisplay.push_exception()
200 # Setup maemo specifics
204 except (ImportError, OSError):
208 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
209 device = osso.DeviceState(self._osso)
210 device.set_device_state_callback(self._on_device_state_change, 0)
212 _moduleLogger.warning("No device state support")
216 self._alarmHandler = alarm_handler.AlarmHandler()
217 except (ImportError, OSError):
220 with gtk_toolbox.gtk_lock():
221 self._errorDisplay.push_exception()
223 _moduleLogger.warning("No notification support")
224 if hildonize.IS_HILDON_SUPPORTED:
227 self._ledHandler = led_handler.LedHandler()
229 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
230 self._ledHandler = None
232 self._ledHandler = None
236 except (ImportError, OSError):
238 self._connection = None
239 if conic is not None:
240 self._connection = conic.Connection()
241 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
242 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
244 _moduleLogger.warning("No connection support")
246 with gtk_toolbox.gtk_lock():
247 self._errorDisplay.push_exception()
249 # Setup costly backends
256 os.makedirs(constants._data_path_)
260 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
262 self._phoneBackends.update({
263 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
265 with gtk_toolbox.gtk_lock():
266 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
267 self._dialpads.update({
268 self.GV_BACKEND: unifiedDialpad,
270 self._accountViews.update({
271 self.GV_BACKEND: gv_views.AccountInfo(
272 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
275 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
276 self._recentViews.update({
277 self.GV_BACKEND: gv_views.RecentCallsView(
278 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
281 self._messagesViews.update({
282 self.GV_BACKEND: gv_views.MessagesView(
283 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
286 self._contactsViews.update({
287 self.GV_BACKEND: gv_views.ContactsView(
288 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
292 fsContactsPath = os.path.join(constants._data_path_, "contacts")
293 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
295 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
296 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
297 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
298 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
301 self._phoneBackends[self.GV_BACKEND],
304 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
305 self._contactsViews[self.GV_BACKEND].append(mergedBook)
306 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
307 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
310 "on_paste": self._on_paste,
311 "on_refresh": self._on_menu_refresh,
312 "on_clearcookies_clicked": self._on_clearcookies_clicked,
313 "on_about_activate": self._on_about_activate,
315 if hildonize.GTK_MENU_USED:
316 self._widgetTree.signal_autoconnect(callbackMapping)
317 self._notebook.connect("switch-page", self._on_notebook_switch_page)
318 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
320 with gtk_toolbox.gtk_lock():
321 self._originalCurrentLabels = [
322 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
323 for pageIndex in xrange(self._notebook.get_n_pages())
325 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
326 self._notebookTapHandler.enable()
327 self._notebookTapHandler.on_tap = self._reset_tab_refresh
328 self._notebookTapHandler.on_hold = self._on_tab_refresh
329 self._notebookTapHandler.on_holding = self._set_tab_refresh
330 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
332 self._initDone = True
334 config = ConfigParser.SafeConfigParser()
335 config.read(constants._user_settings_)
336 with gtk_toolbox.gtk_lock():
337 self.load_settings(config)
339 with gtk_toolbox.gtk_lock():
340 self._errorDisplay.push_exception()
342 self._spawn_attempt_login(2)
344 def _spawn_attempt_login(self, *args):
345 self._loginSink.send(args)
347 def _attempt_login(self, numOfAttempts = 10, force = False):
349 @note This must be run outside of the UI lock
352 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
353 assert self._initDone, "Attempting login before app is fully loaded"
355 serviceId = self.NULL_BACKEND
358 with gtk_toolbox.gtk_lock():
359 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
361 self.refresh_session()
362 serviceId = self._defaultBackendId
365 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
367 with gtk_toolbox.gtk_lock():
368 hildonize.show_busy_banner_end(banner)
371 loggedIn, serviceId = self._login_by_user(numOfAttempts)
373 with gtk_toolbox.gtk_lock():
374 self._change_loggedin_status(serviceId)
376 hildonize.show_information_banner(self._window, "Logged In")
378 hildonize.show_information_banner(self._window, "Login Failed")
380 with gtk_toolbox.gtk_lock():
381 self._errorDisplay.push_exception()
383 def refresh_session(self):
385 @note Thread agnostic
387 assert self._initDone, "Attempting login before app is fully loaded"
391 loggedIn = self._login_by_cookie()
393 loggedIn = self._login_by_settings()
396 raise RuntimeError("Login Failed")
398 def _login_by_cookie(self):
400 @note Thread agnostic
402 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
404 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
407 def _login_by_settings(self):
409 @note Thread agnostic
411 username, password = self._credentials
412 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
414 self._credentials = username, password
415 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
418 def _login_by_user(self, numOfAttempts):
420 @note This must be run outside of the UI lock
422 loggedIn, (username, password) = False, self._credentials
423 tmpServiceId = self.GV_BACKEND
424 for attemptCount in xrange(numOfAttempts):
427 with gtk_toolbox.gtk_lock():
428 credentials = self._credentialsDialog.request_credentials(
429 defaultCredentials = self._credentials
431 if not self._phoneBackends[tmpServiceId].get_callback_number():
432 # subtle reminder to the users to configure things
433 self._notebook.set_current_page(self.ACCOUNT_TAB)
434 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
436 username, password = credentials
437 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
439 with gtk_toolbox.gtk_lock():
440 hildonize.show_busy_banner_end(banner)
443 serviceId = tmpServiceId
444 self._credentials = username, password
445 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
447 serviceId = self.NULL_BACKEND
448 self._notebook.set_current_page(self.ACCOUNT_TAB)
450 return loggedIn, serviceId
452 def _select_action(self, action, number, message):
453 self.refresh_session()
454 if action == "select":
455 self._dialpads[self._selectedBackendId].set_number(number)
456 self._notebook.set_current_page(self.KEYPAD_TAB)
457 elif action == "dial":
458 self._on_dial_clicked(number)
459 elif action == "sms":
460 self._on_sms_clicked(number, message)
462 assert False, "Unknown action: %s" % action
464 def _change_loggedin_status(self, newStatus):
465 oldStatus = self._selectedBackendId
466 if oldStatus == newStatus:
469 self._dialpads[oldStatus].disable()
470 self._accountViews[oldStatus].disable()
471 self._recentViews[oldStatus].disable()
472 self._messagesViews[oldStatus].disable()
473 self._contactsViews[oldStatus].disable()
475 self._dialpads[newStatus].enable()
476 self._accountViews[newStatus].enable()
477 self._recentViews[newStatus].enable()
478 self._messagesViews[newStatus].enable()
479 self._contactsViews[newStatus].enable()
481 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
482 self._phoneBackends[self._selectedBackendId].set_sane_callback()
484 self._selectedBackendId = newStatus
486 self._accountViews[self._selectedBackendId].update()
487 self._refresh_active_tab()
489 def load_settings(self, config):
494 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
496 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
497 for i in xrange(len(self._credentials))
500 base64.b64decode(blob)
503 self._credentials = tuple(creds)
505 if self._alarmHandler is not None:
506 self._alarmHandler.load_settings(config, "alarm")
507 except ConfigParser.NoOptionError, e:
508 _moduleLogger.exception(
509 "Settings file %s is missing section %s" % (
510 constants._user_settings_,
514 except ConfigParser.NoSectionError, e:
515 _moduleLogger.exception(
516 "Settings file %s is missing section %s" % (
517 constants._user_settings_,
522 for backendId, view in itertools.chain(
523 self._dialpads.iteritems(),
524 self._accountViews.iteritems(),
525 self._messagesViews.iteritems(),
526 self._recentViews.iteritems(),
527 self._contactsViews.iteritems(),
529 sectionName = "%s - %s" % (backendId, view.name())
531 view.load_settings(config, sectionName)
532 except ConfigParser.NoOptionError, e:
533 _moduleLogger.exception(
534 "Settings file %s is missing section %s" % (
535 constants._user_settings_,
539 except ConfigParser.NoSectionError, e:
540 _moduleLogger.exception(
541 "Settings file %s is missing section %s" % (
542 constants._user_settings_,
548 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
549 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
550 hildonize.window_to_landscape(self._window)
551 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
552 hildonize.window_to_portrait(self._window)
553 except ConfigParser.NoOptionError, e:
554 _moduleLogger.exception(
555 "Settings file %s is missing section %s" % (
556 constants._user_settings_,
560 except ConfigParser.NoSectionError, e:
561 _moduleLogger.exception(
562 "Settings file %s is missing section %s" % (
563 constants._user_settings_,
568 def save_settings(self, config):
570 @note Thread Agnostic
572 config.add_section(constants.__pretty_app_name__)
573 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
574 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
575 for i, value in enumerate(self._credentials):
576 blob = base64.b64encode(value)
577 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
578 config.add_section("alarm")
579 if self._alarmHandler is not None:
580 self._alarmHandler.save_settings(config, "alarm")
582 for backendId, view in itertools.chain(
583 self._dialpads.iteritems(),
584 self._accountViews.iteritems(),
585 self._messagesViews.iteritems(),
586 self._recentViews.iteritems(),
587 self._contactsViews.iteritems(),
589 sectionName = "%s - %s" % (backendId, view.name())
590 config.add_section(sectionName)
591 view.save_settings(config, sectionName)
593 def _save_settings(self):
595 @note Thread Agnostic
597 config = ConfigParser.SafeConfigParser()
598 self.save_settings(config)
599 with open(constants._user_settings_, "wb") as configFile:
600 config.write(configFile)
602 def _refresh_active_tab(self):
603 pageIndex = self._notebook.get_current_page()
604 if pageIndex == self.CONTACTS_TAB:
605 self._contactsViews[self._selectedBackendId].update(force=True)
606 elif pageIndex == self.RECENT_TAB:
607 self._recentViews[self._selectedBackendId].update(force=True)
608 elif pageIndex == self.MESSAGES_TAB:
609 self._messagesViews[self._selectedBackendId].update(force=True)
611 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
612 if self._ledHandler is not None:
613 self._ledHandler.off()
615 def _on_close(self, *args, **kwds):
617 if self._osso is not None:
621 self._save_settings()
625 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
627 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
628 For system_inactivity, we have no background tasks to pause
630 @note Hildon specific
634 for backendId in self.BACKENDS:
635 self._phoneBackends[backendId].clear_caches()
636 self._contactsViews[self._selectedBackendId].clear_caches()
639 if save_unsaved_data or shutdown:
640 self._save_settings()
642 self._errorDisplay.push_exception()
644 def _on_connection_change(self, connection, event, magicIdentifier):
646 @note Hildon specific
651 status = event.get_status()
652 error = event.get_error()
653 iap_id = event.get_iap_id()
654 bearer = event.get_bearer_type()
656 if status == conic.STATUS_CONNECTED:
658 self._spawn_attempt_login(2)
659 elif status == conic.STATUS_DISCONNECTED:
661 self._defaultBackendId = self._selectedBackendId
662 self._change_loggedin_status(self.NULL_BACKEND)
664 self._errorDisplay.push_exception()
666 def _on_window_state_change(self, widget, event, *args):
668 @note Hildon specific
671 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
672 self._isFullScreen = True
674 self._isFullScreen = False
676 self._errorDisplay.push_exception()
678 def _on_key_press(self, widget, event, *args):
680 @note Hildon specific
682 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
685 event.keyval == gtk.keysyms.F6 or
686 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
688 if self._isFullScreen:
689 self._window.unfullscreen()
691 self._window.fullscreen()
693 self._errorDisplay.push_exception()
695 def _on_clearcookies_clicked(self, *args):
697 self._phoneBackends[self._selectedBackendId].logout()
698 self._accountViews[self._selectedBackendId].clear()
699 self._recentViews[self._selectedBackendId].clear()
700 self._messagesViews[self._selectedBackendId].clear()
701 self._contactsViews[self._selectedBackendId].clear()
702 self._change_loggedin_status(self.NULL_BACKEND)
704 self._spawn_attempt_login(2, True)
706 self._errorDisplay.push_exception()
708 def _on_notebook_switch_page(self, notebook, page, pageIndex):
710 self._reset_tab_refresh()
712 didRecentUpdate = False
713 didMessagesUpdate = False
715 if pageIndex == self.RECENT_TAB:
716 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
717 elif pageIndex == self.MESSAGES_TAB:
718 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
719 elif pageIndex == self.CONTACTS_TAB:
720 self._contactsViews[self._selectedBackendId].update()
721 elif pageIndex == self.ACCOUNT_TAB:
722 self._accountViews[self._selectedBackendId].update()
724 if didRecentUpdate or didMessagesUpdate:
725 if self._ledHandler is not None:
726 self._ledHandler.off()
728 self._errorDisplay.push_exception()
730 def _set_tab_refresh(self, *args):
732 pageIndex = self._notebook.get_current_page()
733 child = self._notebook.get_nth_page(pageIndex)
734 self._notebook.get_tab_label(child).set_text("Refresh?")
736 self._errorDisplay.push_exception()
739 def _reset_tab_refresh(self, *args):
741 pageIndex = self._notebook.get_current_page()
742 child = self._notebook.get_nth_page(pageIndex)
743 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
745 self._errorDisplay.push_exception()
748 def _on_tab_refresh(self, *args):
750 self._refresh_active_tab()
751 self._reset_tab_refresh()
753 self._errorDisplay.push_exception()
756 def _on_sms_clicked(self, number, message):
758 assert number, "No number specified"
759 assert message, "Empty message"
761 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
764 self._errorDisplay.push_exception()
768 self._errorDisplay.push_message(
769 "Backend link with GoogleVoice is not working, please try again"
775 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
776 hildonize.show_information_banner(self._window, "Sending to %s" % number)
779 self._errorDisplay.push_exception()
782 self._dialpads[self._selectedBackendId].clear()
784 self._errorDisplay.push_exception()
786 def _on_dial_clicked(self, number):
788 assert number, "No number to call"
790 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
793 self._errorDisplay.push_exception()
797 self._errorDisplay.push_message(
798 "Backend link with GoogleVoice is not working, please try again"
804 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
805 self._phoneBackends[self._selectedBackendId].dial(number)
806 hildonize.show_information_banner(self._window, "Calling %s" % number)
809 self._errorDisplay.push_exception()
812 self._dialpads[self._selectedBackendId].clear()
814 self._errorDisplay.push_exception()
816 def _on_menu_refresh(self, *args):
818 self._refresh_active_tab()
820 self._errorDisplay.push_exception()
822 def _on_paste(self, *args):
824 contents = self._clipboard.wait_for_text()
825 if contents is not None:
826 self._dialpads[self._selectedBackendId].set_number(contents)
828 self._errorDisplay.push_exception()
830 def _on_about_activate(self, *args):
832 dlg = gtk.AboutDialog()
833 dlg.set_name(constants.__pretty_app_name__)
834 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
835 dlg.set_copyright("Copyright 2008 - LGPL")
836 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
837 dlg.set_website("http://gc-dialer.garage.maemo.org/")
838 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
842 self._errorDisplay.push_exception()
848 failureCount, testCount = doctest.testmod()
850 print "Tests Successful"
857 _lock_file = os.path.join(constants._data_path_, ".lock")
858 #with gtk_toolbox.flock(_lock_file, 0):
859 gtk.gdk.threads_init()
861 if hildonize.IS_HILDON_SUPPORTED:
862 gtk.set_application_name(constants.__pretty_app_name__)
863 handle = Dialcentral()
867 class DummyOptions(object):
873 if __name__ == "__main__":
874 logging.basicConfig(level=logging.DEBUG)
876 if len(sys.argv) > 1:
882 if optparse is not None:
883 parser = optparse.OptionParser()
884 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
885 (commandOptions, commandArgs) = parser.parse_args()
887 commandOptions = DummyOptions()
890 if commandOptions.test: