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))
134 "addressbookSelectButton",
139 "callbackSelectButton",
140 "minutesEntryButton",
142 "phoneTypeSelection",
144 hildonize.set_button_thumb_selectable(self._widgetTree.get_widget(button))
146 replacementButtons = [gtk.Button("Test")]
147 menu = hildonize.hildonize_menu(
149 self._widgetTree.get_widget("dialpad_menubar"),
153 self._window.connect("key-press-event", self._on_key_press)
154 self._window.connect("window-state-event", self._on_window_state_change)
155 if not hildonize.IS_HILDON_SUPPORTED:
156 _moduleLogger.warning("No hildonization support")
158 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
160 self._window.connect("destroy", self._on_close)
161 self._window.set_default_size(800, 300)
162 self._window.show_all()
164 self._loginSink = gtk_toolbox.threaded_stage(
167 gtk_toolbox.null_sink(),
171 backgroundSetup = threading.Thread(target=self._idle_setup)
172 backgroundSetup.setDaemon(True)
173 backgroundSetup.start()
175 def _idle_setup(self):
177 If something can be done after the UI loads, push it here so it's not blocking the UI
179 # Barebones UI handlers
184 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
185 with gtk_toolbox.gtk_lock():
186 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
187 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
188 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
189 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
190 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
192 self._dialpads[self._selectedBackendId].enable()
193 self._accountViews[self._selectedBackendId].enable()
194 self._recentViews[self._selectedBackendId].enable()
195 self._messagesViews[self._selectedBackendId].enable()
196 self._contactsViews[self._selectedBackendId].enable()
198 with gtk_toolbox.gtk_lock():
199 self._errorDisplay.push_exception()
201 # Setup maemo specifics
205 except (ImportError, OSError):
209 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
210 device = osso.DeviceState(self._osso)
211 device.set_device_state_callback(self._on_device_state_change, 0)
213 _moduleLogger.warning("No device state support")
217 self._alarmHandler = alarm_handler.AlarmHandler()
218 except (ImportError, OSError):
221 with gtk_toolbox.gtk_lock():
222 self._errorDisplay.push_exception()
224 _moduleLogger.warning("No notification support")
225 if hildonize.IS_HILDON_SUPPORTED:
228 self._ledHandler = led_handler.LedHandler()
230 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
231 self._ledHandler = None
233 self._ledHandler = None
237 except (ImportError, OSError):
239 self._connection = None
240 if conic is not None:
241 self._connection = conic.Connection()
242 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
243 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
245 _moduleLogger.warning("No connection support")
247 with gtk_toolbox.gtk_lock():
248 self._errorDisplay.push_exception()
250 # Setup costly backends
257 os.makedirs(constants._data_path_)
261 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
263 self._phoneBackends.update({
264 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
266 with gtk_toolbox.gtk_lock():
267 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
268 self._dialpads.update({
269 self.GV_BACKEND: unifiedDialpad,
271 self._accountViews.update({
272 self.GV_BACKEND: gv_views.AccountInfo(
273 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
276 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
277 self._recentViews.update({
278 self.GV_BACKEND: gv_views.RecentCallsView(
279 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
282 self._messagesViews.update({
283 self.GV_BACKEND: gv_views.MessagesView(
284 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
287 self._contactsViews.update({
288 self.GV_BACKEND: gv_views.ContactsView(
289 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
293 fsContactsPath = os.path.join(constants._data_path_, "contacts")
294 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
296 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
297 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
298 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
299 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
302 self._phoneBackends[self.GV_BACKEND],
305 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
306 self._contactsViews[self.GV_BACKEND].append(mergedBook)
307 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
308 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
311 "on_paste": self._on_paste,
312 "on_refresh": self._on_menu_refresh,
313 "on_clearcookies_clicked": self._on_clearcookies_clicked,
314 "on_about_activate": self._on_about_activate,
316 if hildonize.GTK_MENU_USED:
317 self._widgetTree.signal_autoconnect(callbackMapping)
318 self._notebook.connect("switch-page", self._on_notebook_switch_page)
319 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
321 with gtk_toolbox.gtk_lock():
322 self._originalCurrentLabels = [
323 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
324 for pageIndex in xrange(self._notebook.get_n_pages())
326 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
327 self._notebookTapHandler.enable()
328 self._notebookTapHandler.on_tap = self._reset_tab_refresh
329 self._notebookTapHandler.on_hold = self._on_tab_refresh
330 self._notebookTapHandler.on_holding = self._set_tab_refresh
331 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
333 self._initDone = True
335 config = ConfigParser.SafeConfigParser()
336 config.read(constants._user_settings_)
337 with gtk_toolbox.gtk_lock():
338 self.load_settings(config)
340 with gtk_toolbox.gtk_lock():
341 self._errorDisplay.push_exception()
343 self._spawn_attempt_login(2)
345 def _spawn_attempt_login(self, *args):
346 self._loginSink.send(args)
348 def _attempt_login(self, numOfAttempts = 10, force = False):
350 @note This must be run outside of the UI lock
353 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
354 assert self._initDone, "Attempting login before app is fully loaded"
356 serviceId = self.NULL_BACKEND
359 with gtk_toolbox.gtk_lock():
360 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
362 self.refresh_session()
363 serviceId = self._defaultBackendId
366 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
368 with gtk_toolbox.gtk_lock():
369 hildonize.show_busy_banner_end(banner)
372 loggedIn, serviceId = self._login_by_user(numOfAttempts)
374 with gtk_toolbox.gtk_lock():
375 self._change_loggedin_status(serviceId)
377 hildonize.show_information_banner(self._window, "Logged In")
379 hildonize.show_information_banner(self._window, "Login Failed")
381 with gtk_toolbox.gtk_lock():
382 self._errorDisplay.push_exception()
384 def refresh_session(self):
386 @note Thread agnostic
388 assert self._initDone, "Attempting login before app is fully loaded"
392 loggedIn = self._login_by_cookie()
394 loggedIn = self._login_by_settings()
397 raise RuntimeError("Login Failed")
399 def _login_by_cookie(self):
401 @note Thread agnostic
403 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
405 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
408 def _login_by_settings(self):
410 @note Thread agnostic
412 username, password = self._credentials
413 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
415 self._credentials = username, password
416 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
419 def _login_by_user(self, numOfAttempts):
421 @note This must be run outside of the UI lock
423 loggedIn, (username, password) = False, self._credentials
424 tmpServiceId = self.GV_BACKEND
425 for attemptCount in xrange(numOfAttempts):
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)
435 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
437 username, password = credentials
438 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
440 with gtk_toolbox.gtk_lock():
441 hildonize.show_busy_banner_end(banner)
444 serviceId = tmpServiceId
445 self._credentials = username, password
446 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
448 serviceId = self.NULL_BACKEND
449 self._notebook.set_current_page(self.ACCOUNT_TAB)
451 return loggedIn, serviceId
453 def _select_action(self, action, number, message):
454 self.refresh_session()
455 if action == "select":
456 self._dialpads[self._selectedBackendId].set_number(number)
457 self._notebook.set_current_page(self.KEYPAD_TAB)
458 elif action == "dial":
459 self._on_dial_clicked(number)
460 elif action == "sms":
461 self._on_sms_clicked(number, message)
463 assert False, "Unknown action: %s" % action
465 def _change_loggedin_status(self, newStatus):
466 oldStatus = self._selectedBackendId
467 if oldStatus == newStatus:
470 self._dialpads[oldStatus].disable()
471 self._accountViews[oldStatus].disable()
472 self._recentViews[oldStatus].disable()
473 self._messagesViews[oldStatus].disable()
474 self._contactsViews[oldStatus].disable()
476 self._dialpads[newStatus].enable()
477 self._accountViews[newStatus].enable()
478 self._recentViews[newStatus].enable()
479 self._messagesViews[newStatus].enable()
480 self._contactsViews[newStatus].enable()
482 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
483 self._phoneBackends[self._selectedBackendId].set_sane_callback()
485 self._selectedBackendId = newStatus
487 self._accountViews[self._selectedBackendId].update()
488 self._refresh_active_tab()
490 def load_settings(self, config):
495 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
497 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
498 for i in xrange(len(self._credentials))
501 base64.b64decode(blob)
504 self._credentials = tuple(creds)
506 if self._alarmHandler is not None:
507 self._alarmHandler.load_settings(config, "alarm")
508 except ConfigParser.NoOptionError, e:
509 _moduleLogger.exception(
510 "Settings file %s is missing section %s" % (
511 constants._user_settings_,
515 except ConfigParser.NoSectionError, e:
516 _moduleLogger.exception(
517 "Settings file %s is missing section %s" % (
518 constants._user_settings_,
523 for backendId, view in itertools.chain(
524 self._dialpads.iteritems(),
525 self._accountViews.iteritems(),
526 self._messagesViews.iteritems(),
527 self._recentViews.iteritems(),
528 self._contactsViews.iteritems(),
530 sectionName = "%s - %s" % (backendId, view.name())
532 view.load_settings(config, sectionName)
533 except ConfigParser.NoOptionError, e:
534 _moduleLogger.exception(
535 "Settings file %s is missing section %s" % (
536 constants._user_settings_,
540 except ConfigParser.NoSectionError, e:
541 _moduleLogger.exception(
542 "Settings file %s is missing section %s" % (
543 constants._user_settings_,
549 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
550 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
551 hildonize.window_to_landscape(self._window)
552 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
553 hildonize.window_to_portrait(self._window)
554 except ConfigParser.NoOptionError, e:
555 _moduleLogger.exception(
556 "Settings file %s is missing section %s" % (
557 constants._user_settings_,
561 except ConfigParser.NoSectionError, e:
562 _moduleLogger.exception(
563 "Settings file %s is missing section %s" % (
564 constants._user_settings_,
569 def save_settings(self, config):
571 @note Thread Agnostic
573 config.add_section(constants.__pretty_app_name__)
574 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
575 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
576 for i, value in enumerate(self._credentials):
577 blob = base64.b64encode(value)
578 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
579 config.add_section("alarm")
580 if self._alarmHandler is not None:
581 self._alarmHandler.save_settings(config, "alarm")
583 for backendId, view in itertools.chain(
584 self._dialpads.iteritems(),
585 self._accountViews.iteritems(),
586 self._messagesViews.iteritems(),
587 self._recentViews.iteritems(),
588 self._contactsViews.iteritems(),
590 sectionName = "%s - %s" % (backendId, view.name())
591 config.add_section(sectionName)
592 view.save_settings(config, sectionName)
594 def _save_settings(self):
596 @note Thread Agnostic
598 config = ConfigParser.SafeConfigParser()
599 self.save_settings(config)
600 with open(constants._user_settings_, "wb") as configFile:
601 config.write(configFile)
603 def _refresh_active_tab(self):
604 pageIndex = self._notebook.get_current_page()
605 if pageIndex == self.CONTACTS_TAB:
606 self._contactsViews[self._selectedBackendId].update(force=True)
607 elif pageIndex == self.RECENT_TAB:
608 self._recentViews[self._selectedBackendId].update(force=True)
609 elif pageIndex == self.MESSAGES_TAB:
610 self._messagesViews[self._selectedBackendId].update(force=True)
612 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
613 if self._ledHandler is not None:
614 self._ledHandler.off()
616 def _on_close(self, *args, **kwds):
618 if self._osso is not None:
622 self._save_settings()
626 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
628 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
629 For system_inactivity, we have no background tasks to pause
631 @note Hildon specific
635 for backendId in self.BACKENDS:
636 self._phoneBackends[backendId].clear_caches()
637 self._contactsViews[self._selectedBackendId].clear_caches()
640 if save_unsaved_data or shutdown:
641 self._save_settings()
643 self._errorDisplay.push_exception()
645 def _on_connection_change(self, connection, event, magicIdentifier):
647 @note Hildon specific
652 status = event.get_status()
653 error = event.get_error()
654 iap_id = event.get_iap_id()
655 bearer = event.get_bearer_type()
657 if status == conic.STATUS_CONNECTED:
659 self._spawn_attempt_login(2)
660 elif status == conic.STATUS_DISCONNECTED:
662 self._defaultBackendId = self._selectedBackendId
663 self._change_loggedin_status(self.NULL_BACKEND)
665 self._errorDisplay.push_exception()
667 def _on_window_state_change(self, widget, event, *args):
669 @note Hildon specific
672 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
673 self._isFullScreen = True
675 self._isFullScreen = False
677 self._errorDisplay.push_exception()
679 def _on_key_press(self, widget, event, *args):
681 @note Hildon specific
683 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
686 event.keyval == gtk.keysyms.F6 or
687 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
689 if self._isFullScreen:
690 self._window.unfullscreen()
692 self._window.fullscreen()
694 self._errorDisplay.push_exception()
696 def _on_clearcookies_clicked(self, *args):
698 self._phoneBackends[self._selectedBackendId].logout()
699 self._accountViews[self._selectedBackendId].clear()
700 self._recentViews[self._selectedBackendId].clear()
701 self._messagesViews[self._selectedBackendId].clear()
702 self._contactsViews[self._selectedBackendId].clear()
703 self._change_loggedin_status(self.NULL_BACKEND)
705 self._spawn_attempt_login(2, True)
707 self._errorDisplay.push_exception()
709 def _on_notebook_switch_page(self, notebook, page, pageIndex):
711 self._reset_tab_refresh()
713 didRecentUpdate = False
714 didMessagesUpdate = False
716 if pageIndex == self.RECENT_TAB:
717 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
718 elif pageIndex == self.MESSAGES_TAB:
719 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
720 elif pageIndex == self.CONTACTS_TAB:
721 self._contactsViews[self._selectedBackendId].update()
722 elif pageIndex == self.ACCOUNT_TAB:
723 self._accountViews[self._selectedBackendId].update()
725 if didRecentUpdate or didMessagesUpdate:
726 if self._ledHandler is not None:
727 self._ledHandler.off()
729 self._errorDisplay.push_exception()
731 def _set_tab_refresh(self, *args):
733 pageIndex = self._notebook.get_current_page()
734 child = self._notebook.get_nth_page(pageIndex)
735 self._notebook.get_tab_label(child).set_text("Refresh?")
737 self._errorDisplay.push_exception()
740 def _reset_tab_refresh(self, *args):
742 pageIndex = self._notebook.get_current_page()
743 child = self._notebook.get_nth_page(pageIndex)
744 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
746 self._errorDisplay.push_exception()
749 def _on_tab_refresh(self, *args):
751 self._refresh_active_tab()
752 self._reset_tab_refresh()
754 self._errorDisplay.push_exception()
757 def _on_sms_clicked(self, number, message):
759 assert number, "No number specified"
760 assert message, "Empty message"
762 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
765 self._errorDisplay.push_exception()
769 self._errorDisplay.push_message(
770 "Backend link with GoogleVoice is not working, please try again"
776 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
777 hildonize.show_information_banner(self._window, "Sending to %s" % number)
780 self._errorDisplay.push_exception()
783 self._dialpads[self._selectedBackendId].clear()
785 self._errorDisplay.push_exception()
787 def _on_dial_clicked(self, number):
789 assert number, "No number to call"
791 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
794 self._errorDisplay.push_exception()
798 self._errorDisplay.push_message(
799 "Backend link with GoogleVoice is not working, please try again"
805 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
806 self._phoneBackends[self._selectedBackendId].dial(number)
807 hildonize.show_information_banner(self._window, "Calling %s" % number)
810 self._errorDisplay.push_exception()
813 self._dialpads[self._selectedBackendId].clear()
815 self._errorDisplay.push_exception()
817 def _on_menu_refresh(self, *args):
819 self._refresh_active_tab()
821 self._errorDisplay.push_exception()
823 def _on_paste(self, *args):
825 contents = self._clipboard.wait_for_text()
826 if contents is not None:
827 self._dialpads[self._selectedBackendId].set_number(contents)
829 self._errorDisplay.push_exception()
831 def _on_about_activate(self, *args):
833 dlg = gtk.AboutDialog()
834 dlg.set_name(constants.__pretty_app_name__)
835 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
836 dlg.set_copyright("Copyright 2008 - LGPL")
837 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
838 dlg.set_website("http://gc-dialer.garage.maemo.org/")
839 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
843 self._errorDisplay.push_exception()
849 failureCount, testCount = doctest.testmod()
851 print "Tests Successful"
858 _lock_file = os.path.join(constants._data_path_, ".lock")
859 #with gtk_toolbox.flock(_lock_file, 0):
860 gtk.gdk.threads_init()
862 if hildonize.IS_HILDON_SUPPORTED:
863 gtk.set_application_name(constants.__pretty_app_name__)
864 handle = Dialcentral()
868 class DummyOptions(object):
874 if __name__ == "__main__":
875 logging.basicConfig(level=logging.DEBUG)
877 if len(sys.argv) > 1:
883 if optparse is not None:
884 parser = optparse.OptionParser()
885 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
886 (commandOptions, commandArgs) = parser.parse_args()
888 commandOptions = DummyOptions()
891 if commandOptions.test: