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 scrollingWidgetName in (
119 'recent_scrolledwindow',
120 'message_scrolledwindow',
121 'contacts_scrolledwindow',
122 "smsMessages_scrolledwindow",
124 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
125 assert scrollingWidget is not None, scrollingWidgetName
126 hildonize.hildonize_scrollwindow(scrollingWidget)
127 for scrollingWidgetName in (
128 "smsMessage_scrolledEntry",
130 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
131 assert scrollingWidget is not None, scrollingWidgetName
132 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
136 "addressbookSelectButton",
140 "callbackSelectButton",
141 "minutesEntryButton",
143 "phoneTypeSelection",
145 button = self._widgetTree.get_widget(buttonName)
146 assert button is not None, buttonName
147 hildonize.set_button_thumb_selectable(button)
149 replacementButtons = [gtk.Button("Test")]
150 menu = hildonize.hildonize_menu(
152 self._widgetTree.get_widget("dialpad_menubar"),
156 self._window.connect("key-press-event", self._on_key_press)
157 self._window.connect("window-state-event", self._on_window_state_change)
158 if not hildonize.IS_HILDON_SUPPORTED:
159 _moduleLogger.warning("No hildonization support")
161 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
163 self._window.connect("destroy", self._on_close)
164 self._window.set_default_size(800, 300)
165 self._window.show_all()
167 self._loginSink = gtk_toolbox.threaded_stage(
170 gtk_toolbox.null_sink(),
174 backgroundSetup = threading.Thread(target=self._idle_setup)
175 backgroundSetup.setDaemon(True)
176 backgroundSetup.start()
178 def _idle_setup(self):
180 If something can be done after the UI loads, push it here so it's not blocking the UI
182 # Barebones UI handlers
187 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
188 with gtk_toolbox.gtk_lock():
189 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
190 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
191 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
192 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
193 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
195 self._dialpads[self._selectedBackendId].enable()
196 self._accountViews[self._selectedBackendId].enable()
197 self._recentViews[self._selectedBackendId].enable()
198 self._messagesViews[self._selectedBackendId].enable()
199 self._contactsViews[self._selectedBackendId].enable()
201 with gtk_toolbox.gtk_lock():
202 self._errorDisplay.push_exception()
204 # Setup maemo specifics
208 except (ImportError, OSError):
212 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
213 device = osso.DeviceState(self._osso)
214 device.set_device_state_callback(self._on_device_state_change, 0)
216 _moduleLogger.warning("No device state support")
220 self._alarmHandler = alarm_handler.AlarmHandler()
221 except (ImportError, OSError):
224 with gtk_toolbox.gtk_lock():
225 self._errorDisplay.push_exception()
227 _moduleLogger.warning("No notification support")
228 if hildonize.IS_HILDON_SUPPORTED:
231 self._ledHandler = led_handler.LedHandler()
233 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
234 self._ledHandler = None
236 self._ledHandler = None
240 except (ImportError, OSError):
242 self._connection = None
243 if conic is not None:
244 self._connection = conic.Connection()
245 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
246 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
248 _moduleLogger.warning("No connection support")
250 with gtk_toolbox.gtk_lock():
251 self._errorDisplay.push_exception()
253 # Setup costly backends
260 os.makedirs(constants._data_path_)
264 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
266 self._phoneBackends.update({
267 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
269 with gtk_toolbox.gtk_lock():
270 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
271 self._dialpads.update({
272 self.GV_BACKEND: unifiedDialpad,
274 self._accountViews.update({
275 self.GV_BACKEND: gv_views.AccountInfo(
276 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
279 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
280 self._recentViews.update({
281 self.GV_BACKEND: gv_views.RecentCallsView(
282 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
285 self._messagesViews.update({
286 self.GV_BACKEND: gv_views.MessagesView(
287 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
290 self._contactsViews.update({
291 self.GV_BACKEND: gv_views.ContactsView(
292 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
296 fsContactsPath = os.path.join(constants._data_path_, "contacts")
297 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
299 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
300 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
301 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
302 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
305 self._phoneBackends[self.GV_BACKEND],
308 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
309 self._contactsViews[self.GV_BACKEND].append(mergedBook)
310 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
311 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
314 "on_paste": self._on_paste,
315 "on_refresh": self._on_menu_refresh,
316 "on_clearcookies_clicked": self._on_clearcookies_clicked,
317 "on_about_activate": self._on_about_activate,
319 if hildonize.GTK_MENU_USED:
320 self._widgetTree.signal_autoconnect(callbackMapping)
321 self._notebook.connect("switch-page", self._on_notebook_switch_page)
322 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
324 with gtk_toolbox.gtk_lock():
325 self._originalCurrentLabels = [
326 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
327 for pageIndex in xrange(self._notebook.get_n_pages())
329 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
330 self._notebookTapHandler.enable()
331 self._notebookTapHandler.on_tap = self._reset_tab_refresh
332 self._notebookTapHandler.on_hold = self._on_tab_refresh
333 self._notebookTapHandler.on_holding = self._set_tab_refresh
334 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
336 self._initDone = True
338 config = ConfigParser.SafeConfigParser()
339 config.read(constants._user_settings_)
340 with gtk_toolbox.gtk_lock():
341 self.load_settings(config)
343 with gtk_toolbox.gtk_lock():
344 self._errorDisplay.push_exception()
346 self._spawn_attempt_login()
348 def _spawn_attempt_login(self, *args):
349 self._loginSink.send(args)
351 def _attempt_login(self, force = False):
353 @note This must be run outside of the UI lock
356 assert self._initDone, "Attempting login before app is fully loaded"
358 serviceId = self.NULL_BACKEND
361 with gtk_toolbox.gtk_lock():
362 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
364 self.refresh_session()
365 serviceId = self._defaultBackendId
368 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
370 with gtk_toolbox.gtk_lock():
371 hildonize.show_busy_banner_end(banner)
374 loggedIn, serviceId = self._login_by_user()
376 with gtk_toolbox.gtk_lock():
377 self._change_loggedin_status(serviceId)
379 hildonize.show_information_banner(self._window, "Logged In")
381 hildonize.show_information_banner(self._window, "Login Failed")
383 with gtk_toolbox.gtk_lock():
384 self._errorDisplay.push_exception()
386 def refresh_session(self):
388 @note Thread agnostic
390 assert self._initDone, "Attempting login before app is fully loaded"
394 loggedIn = self._login_by_cookie()
396 loggedIn = self._login_by_settings()
399 raise RuntimeError("Login Failed")
401 def _login_by_cookie(self):
403 @note Thread agnostic
405 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
407 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
410 def _login_by_settings(self):
412 @note Thread agnostic
414 username, password = self._credentials
415 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
417 self._credentials = username, password
418 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
421 def _login_by_user(self):
423 @note This must be run outside of the UI lock
425 loggedIn, (username, password) = False, self._credentials
426 tmpServiceId = self.GV_BACKEND
428 with gtk_toolbox.gtk_lock():
429 credentials = self._credentialsDialog.request_credentials(
430 defaultCredentials = self._credentials
432 if not self._phoneBackends[tmpServiceId].get_callback_number():
433 # subtle reminder to the users to configure things
434 self._notebook.set_current_page(self.ACCOUNT_TAB)
436 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
438 username, password = credentials
439 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
441 with gtk_toolbox.gtk_lock():
442 hildonize.show_busy_banner_end(banner)
445 serviceId = tmpServiceId
446 self._credentials = username, password
447 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
449 # Hint to the user that they are not logged in
450 serviceId = self.NULL_BACKEND
451 self._notebook.set_current_page(self.ACCOUNT_TAB)
453 return loggedIn, serviceId
455 def _select_action(self, action, number, message):
456 self.refresh_session()
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()
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(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: