From c387a52f841e8933ed7282d198ed1ece863979fc Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 6 Nov 2012 01:26:05 +0900 Subject: new validation pages in a reusable MVC style using progress indicators inside QTableWidget --- src/leap/base/tests/__init__.py | 0 src/leap/baseapp/mainwindow.py | 7 +- src/leap/gui/firstrunwizard.py | 166 ++++++------- src/leap/gui/mainwindow_rc.py | 97 +++++++- src/leap/gui/progress.py | 261 +++++++++++++++++++++ src/leap/gui/tests/integration/fake_user_signup.py | 6 +- 6 files changed, 442 insertions(+), 95 deletions(-) create mode 100644 src/leap/base/tests/__init__.py create mode 100644 src/leap/gui/progress.py (limited to 'src/leap') diff --git a/src/leap/base/tests/__init__.py b/src/leap/base/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py index 8f359dbf..8e12b5f6 100644 --- a/src/leap/baseapp/mainwindow.py +++ b/src/leap/baseapp/mainwindow.py @@ -147,16 +147,15 @@ class LeapWindow(QtGui.QMainWindow, self.initchecks.begin() -class InitChecksThread(QtCore.QThread): - # XXX rename as a generic QThread class, - # has nothing specific to initchecks +class FunThread(QtCore.QThread): def __init__(self, fun, parent=None): QtCore.QThread.__init__(self, parent) self.fun = fun def run(self): - self.fun() + if self.fun: + self.fun() def begin(self): self.start() diff --git a/src/leap/gui/firstrunwizard.py b/src/leap/gui/firstrunwizard.py index 6b9921d9..7876c3c8 100755 --- a/src/leap/gui/firstrunwizard.py +++ b/src/leap/gui/firstrunwizard.py @@ -19,9 +19,11 @@ from leap.crypto import certs from leap.crypto import leapkeyring from leap.eip import checks as eipchecks from leap.eip import exceptions as eipexceptions -from leap.gui import mainwindow_rc +from leap.gui.progress import ValidationPage from leap.util.coroutines import coroutine +from leap.gui import mainwindow_rc + try: from collections import OrderedDict except ImportError: @@ -101,7 +103,6 @@ class FirstRunWizard(QtGui.QWizard): # XXX ??? ^v self.is_previously_registered = bool(self.eip_username) self.from_login = False - #self.allow_revisit = None pages_dict = OrderedDict(( # (name, WizardPage) @@ -110,13 +111,15 @@ class FirstRunWizard(QtGui.QWizard): SelectProviderPage), ('login', LogInPage), ('providerinfo', ProviderInfoPage), - ('providersetup', ProviderSetupPage), + ('providersetupvalidation', ProviderSetupValidationPage), ('signup', RegisterUserPage), ('connecting', ConnectingPage), ('lastpage', LastPage) )) self.add_pages_from_dict(pages_dict) + self.validation_errors = {} + self.setPixmap( QtGui.QWizard.BannerPixmap, QtGui.QPixmap(':/images/banner.png')) @@ -151,25 +154,11 @@ class FirstRunWizard(QtGui.QWizard): """ return self.pages_dict.keys().index(page_name) - # XXX was trying to allow temporary - # a revisit. this does not work cause visitedPages - # is not called internally. - - #def allow_page_revisit(self, page_name): - #self.allow_revisit = self.get_page_index(page_name) -# - #def visitedPages(self): - #""" - #reimplementation of visitedPages - #that temporary allows to revisit a page - #if allow_revisit is set - #""" - #visited = super(FirstRunWizard, self).visitedPages() - #allow = self.allow_revisit - #if allow: - #visited.remove(allow) - #self.allow_revisit = None - #return visited + def set_validation_error(self, pagename, error): + self.validation_errors[pagename] = error + + def get_validation_error(self, pagename): + return self.validation_errors.get(pagename, None) def set_providerconfig(self, providerconfig): self.providerconfig = providerconfig @@ -447,6 +436,20 @@ class SelectProviderPage(QtGui.QWizardPage): self.certinfoGroup.hide() def validatePage(self): + ################################## + # XXX FIXME! + ################################## + ################################## + ################################## + ################################## + ##### validation skipped !!! ##### + ################################## + ################################## + return True + ################################## + ################################## + ################################## + wizard = self.wizard() netchecker = wizard.netchecker() providercertchecker = wizard.providercertchecker() @@ -559,39 +562,25 @@ class ProviderInfoPage(QtGui.QWizardPage): def nextId(self): wizard = self.wizard() - if not wizard: - return - return wizard.get_page_index('providersetup') + next_ = "providersetupvalidation" + return wizard.get_page_index(next_) -class ProviderSetupPage(QtGui.QWizardPage): +class ProviderSetupValidationPage(ValidationPage): def __init__(self, parent=None): - super(ProviderSetupPage, self).__init__(parent) - - self.setTitle("Provider Setup") - self.setSubTitle("Setting up provider.") + super(ProviderSetupValidationPage, self).__init__(parent) + self.setTitle("Setting up provider") + #self.setSubTitle( + #"auto configuring provider...") self.setPixmap( QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) - self.status = QtGui.QLabel("") - self.progress = QtGui.QProgressBar() - self.progress.setMaximum(100) - self.progress.hide() - - layout = QtGui.QGridLayout() - layout.addWidget(self.status, 0, 1) - layout.addWidget(self.progress, 5, 1) - - self.setLayout(layout) - - def set_status(self, status): - self.status.setText(status) - self.status.setWordWrap(True) - - def fetch_and_validate(self): - # Fake... till you make it... + def _do_checks(self, signal=None): + """ + executes actual checks in a separate thread + """ import time domain = self.field('provider_domain') wizard = self.wizard() @@ -600,7 +589,7 @@ class ProviderSetupPage(QtGui.QWizardPage): pCertChecker = wizard.providercertchecker certchecker = pCertChecker(domain=domain) - self.set_status('Fetching CA certificate') + signal.emit('Fetching CA certificate') self.progress.setValue(30) if pconfig: @@ -615,68 +604,66 @@ class ProviderSetupPage(QtGui.QWizardPage): # (Check with the trusted fingerprints dict # or something smart) - certchecker.download_ca_cert( - uri=ca_cert_uri, - verify=False) + #certchecker.download_ca_cert( + #uri=ca_cert_uri, + #verify=False) + + time.sleep(2) - self.set_status('Checking CA fingerprint') + signal.emit('Checking CA fingerprint') self.progress.setValue(66) - ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) + #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) # XXX get fingerprint dict (types) - sha256_fpr = ca_cert_fingerprint.split('=')[1] + #sha256_fpr = ca_cert_fingerprint.split('=')[1] - validate_fpr = certchecker.check_ca_cert_fingerprint( - fingerprint=sha256_fpr) + #validate_fpr = certchecker.check_ca_cert_fingerprint( + #fingerprint=sha256_fpr) time.sleep(0.5) - if not validate_fpr: + #if not validate_fpr: # XXX update validationMsg # should catch exception - return False + #return False - self.set_status('Validating api certificate') + signal.emit('Validating api certificate') self.progress.setValue(90) - api_uri = pconfig.get('api_uri', None) - try: - api_cert_verified = certchecker.verify_api_https(api_uri) - except requests.exceptions.SSLError as exc: - logger.error('BUG #638. %s' % exc.message) + #api_uri = pconfig.get('api_uri', None) + #try: + #api_cert_verified = certchecker.verify_api_https(api_uri) + #except requests.exceptions.SSLError as exc: + #logger.error('BUG #638. %s' % exc.message) # XXX RAISE! See #638 # bypassing until the hostname is fixed. # We probably should raise yet-another-warning # here saying user that the hostname "XX.XX.XX.XX' does not # match 'foo.bar.baz' - api_cert_verified = True + #api_cert_verified = True - if not api_cert_verified: + #if not api_cert_verified: # XXX update validationMsg # should catch exception - return False + #return False time.sleep(0.5) #ca_cert_path = checker.ca_cert_path self.progress.setValue(100) + signal.emit('end_sentinel') time.sleep(1) - # pagewizard methods - - def initializePage(self): - self.set_status( - 'We are going to contact the provider to get ' - 'the certificates that will be used to stablish ' - 'a secure connection.

Click next to continue.') - self.progress.setValue(0) - self.progress.hide() - - # XXX use a call to "next" instead? - #self.wizard().next() - - def validatePage(self): - self.progress.show() - self.fetch_and_validate() - - return True + def _do_validation(self): + """ + called after _do_checks has finished + (connected to checker thread finished signal) + """ + if self.errors: + print 'going back with errors' + wizard.set_validation_error( + 'signup', 'that name is taken') + self.go_back() + else: + print 'going next' + self.go_next() def nextId(self): wizard = self.wizard() @@ -952,12 +939,15 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): signup = auth.LeapSRPRegister( schema="http", - provider=domain, + #provider=domain, + ########################### + # FIXME! REMOVE DEBUG! + # # debug ----- - #provider="localhost", + provider="localhost", #register_path="timeout", - #port=8000 + port=8000 ) try: ok, req = signup.register_user(username, password) diff --git a/src/leap/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py index be575159..63e9f6be 100644 --- a/src/leap/gui/mainwindow_rc.py +++ b/src/leap/gui/mainwindow_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: Thu Sep 13 16:12:58 2012 +# Created: Tue Nov 6 01:22:11 2012 # by: The Resource Compiler for PyQt (Qt v4.8.2) # # WARNING! All changes made in this file will be lost! @@ -1491,6 +1491,94 @@ qt_resource_data = "\ \xc3\x25\x0d\x25\x35\x01\xd7\x0f\x5b\xb5\x7e\x8e\x93\x83\xff\x0f\ \x92\x04\x28\x92\xfd\x58\xc9\xac\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ +\x00\x00\x05\x5f\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x00\x8e\x00\x00\x00\x8e\ +\x01\x6b\xdf\xd6\xc9\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ +\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ +\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x04\xdc\x49\x44\ +\x41\x54\x48\x89\x8d\x95\x79\x6c\x54\x55\x14\xc6\x7f\xf7\xce\x4c\ +\x3b\xa5\xed\x14\x6b\x3b\x25\x42\x17\x18\x28\x65\xba\x90\x6e\x18\ +\xa3\xc5\x05\xa4\x86\x20\x21\xa0\x0d\x88\x24\x26\x98\x10\x28\x68\ +\x42\x40\x8c\x20\x1a\xa3\x16\x43\x48\x8c\x9a\x50\x08\xc6\x14\xad\ +\x9a\x22\x8d\xa2\xa6\x01\x42\x13\x20\x44\x49\x3b\x2c\x5d\x87\xd2\ +\x05\x64\xb1\xad\x85\x76\xa6\xed\x74\x9b\xf7\xae\x7f\xcc\x62\x9b\ +\x69\x2b\xe7\x9f\x97\x73\x5e\xce\xf7\x9d\x7b\xce\x77\xcf\x15\x4a\ +\x29\xa6\xb3\x95\x55\x0b\x2c\xe1\x26\x73\x8e\x80\x3c\x14\xb9\x20\ +\x24\x4a\xd4\x02\x0e\xa3\x34\x39\x2a\x96\xd7\xba\xa6\xcb\x17\x53\ +\x11\xac\x3b\x97\x91\xaf\x94\x2c\x45\x91\x0d\x88\x29\xf2\x15\xd0\ +\xa0\xeb\xa2\xf8\xe7\xc2\xeb\x17\x1f\x89\xa0\xe8\x44\x46\x98\x77\ +\xa6\xf8\x00\xe4\x1e\x83\x30\x18\x72\xe3\x0a\x48\x8d\x49\xc7\x66\ +\x49\xe7\x71\x73\x02\x12\xc9\x83\x91\x2e\x5a\x5d\x8d\xdc\x74\x37\ +\x52\xdb\x73\x01\xaf\x3e\xa6\x03\x9f\xc7\x98\xfa\xf7\x7e\xf3\x5c\ +\xc7\xf0\x94\x04\xaf\x9c\xc9\x4a\xd3\x05\x15\x40\x66\x72\x54\x2a\ +\xc5\xf6\xfd\xb4\xba\xae\xd2\xe6\xba\xc6\xbd\x81\x16\x06\x46\x7b\ +\x30\x00\xb1\x66\x2b\x89\x51\x69\x24\x5b\x32\x48\xb6\x64\x73\xd8\ +\xf9\x09\x6d\xee\x66\x80\x66\x29\xd5\xfa\x9f\x96\xd5\xd7\x85\x10\ +\xf8\x2a\x97\xb5\x52\xc8\xcc\x75\x29\x6f\x92\x1f\xbf\x94\x32\xe7\ +\xc7\x74\x79\xda\x31\x08\x81\x44\x60\x10\x02\x03\x4c\xf0\xe3\xcc\ +\x4f\xb0\x7a\xfe\x1e\xea\xfa\x1c\xfc\xd8\x76\x04\x4d\xd7\xda\xbc\ +\xa3\x61\x59\xa7\x56\xd5\x7a\x00\x64\x80\xc9\xd7\x16\x32\xd7\xa6\ +\x6c\x26\xde\x6c\xa5\xe4\xca\x66\xee\x0f\xb6\x4d\xd1\xfa\xff\xac\ +\x77\xf8\x6f\xca\x1b\x77\x32\x2b\x2c\x9a\x8d\xf3\x8b\x41\x60\x33\ +\x86\x8f\x95\x04\xfe\x4b\xf0\x0d\x14\xe4\x9e\xe4\xa8\x54\xf2\xe3\ +\x9f\xa5\xbc\xe5\x20\x9a\xd2\xfe\x17\x1c\x40\x20\x88\x09\x4f\xe0\ +\xcf\xfb\x15\x64\xc7\x2e\x21\x35\x26\x13\x50\x3b\xd6\x9c\x5e\x5c\ +\x00\x60\x04\x50\x4a\x96\x1a\x84\xc1\xb0\xd5\xbe\x9f\x63\xcd\x1f\ +\xa1\x29\x0d\x39\x95\x6e\xc6\xd9\x9c\xa8\x34\x5e\xb6\xed\x24\xc9\ +\x92\xc1\xe0\xd8\x43\xca\xea\xb7\xb3\xdd\xfe\x21\xbb\x2e\x6f\x14\ +\xa3\x8c\x94\x02\xe9\x72\x65\xd5\x02\x0b\x8a\xec\xdc\xb8\x02\x9c\ +\xbd\x0e\xee\x3e\x42\x5b\x00\x56\xcd\xdb\xc1\xb6\xec\x63\x24\x59\ +\x32\x00\xb8\xe3\x6e\xe0\xe1\xd0\x1d\x3a\x7a\x2f\xf1\x94\xf5\x05\ +\x00\x7b\x51\x55\x4e\xbc\x0c\x37\x99\x73\x00\x31\xdf\x92\x4e\x73\ +\xdf\x95\x10\xa0\xe4\xe8\x45\x21\xb1\xe5\x49\x6f\xb0\x74\xce\x6b\ +\x08\xff\x08\x3b\x07\x5b\x39\xd5\x7a\xc0\x4f\x74\x8d\x45\x33\xb3\ +\x00\xf0\x86\x69\xb9\x52\x40\x1e\xc0\x3c\x8b\x9d\x5b\xfd\xce\x09\ +\x40\x85\x49\x9b\x78\x2f\xef\x38\x6b\x6d\x6f\x05\x63\xb9\xd6\x97\ +\x28\x4c\xd9\x12\xf4\xbb\x06\xdb\x29\x6b\x78\x9b\x21\xaf\xdb\x47\ +\x36\xd0\xc2\xbc\x68\x3b\x00\x4a\x53\x79\x46\xdf\xf5\x87\x38\x73\ +\x02\xbd\x23\xff\xf8\x24\x28\x24\x9b\x52\xdf\x61\xd9\x9c\x22\x00\ +\x5e\x4c\xdc\x84\xa3\xeb\x0c\x46\x69\x60\x43\xda\x3e\x84\xff\x62\ +\x0f\x8e\xf5\xf1\x6d\xe3\x6e\x3c\x63\xae\xe0\xcc\x46\xb4\x41\x66\ +\x18\x23\x91\x42\xa2\x2b\x3d\xcf\x08\x42\x02\x08\x11\x54\x2c\x11\ +\xc6\x68\x22\x4d\xd1\x13\x4e\xf3\x42\xe2\x06\x52\x2c\x19\x18\x84\ +\x09\x00\x5d\x69\x7c\xdf\xbc\x8f\xbe\x91\x4e\x0c\x62\xa2\x22\x02\ +\x58\x42\x28\x19\x58\x5c\xf4\x0c\x77\x12\x1b\x6e\xf5\x57\xe6\xa2\ +\xb4\x71\x2f\xe5\x2d\x9f\xa1\xfb\xe5\xba\x24\x61\x25\xd6\x88\xa4\ +\x20\x48\x55\xc7\x61\xda\x5d\xa1\x33\x33\x1b\xa3\xf0\x78\x07\xd0\ +\x95\x0e\x88\x1a\x09\x38\x00\xda\xdd\x4d\xcc\xb5\x4c\x1c\x68\xf5\ +\xdd\x0a\xbe\xbb\x51\x12\x02\x72\x7f\xa0\x85\x8b\xf7\x7e\x08\x89\ +\x03\xcc\x8a\x5c\x48\x9b\xbb\xc9\x77\x02\x70\x48\xa3\x34\x39\x00\ +\xd5\xea\x6e\x62\x81\x25\x33\x24\xe1\x52\xe7\x29\x3a\x3d\x1d\x41\ +\x5f\xa1\xa8\x6c\x3d\xe8\xaf\x30\xd4\x66\x47\x67\xd0\xec\xaa\x07\ +\x40\x57\xa2\x56\xfa\xf7\x79\x83\xa3\xe7\x02\xf6\xd8\x7c\xe2\x23\ +\x66\x4f\x48\x50\x4a\xe7\xcc\x5f\xc7\x83\x7e\x4d\xe7\xef\xdc\x72\ +\xd7\x4f\x0a\x6e\x09\x8b\x67\xee\x63\x4f\x73\xb9\xbb\x1a\x50\x37\ +\x2b\x57\x5c\xef\x96\x00\xba\x2e\x8a\xbd\xfa\x98\x7e\xd4\xf9\x29\ +\x9b\xd3\xde\x0f\xaa\x24\x60\x8d\x0f\xff\xe0\xfc\xbd\x0a\xaa\xef\ +\x94\xf3\x6b\xfb\x57\x93\x82\x03\x14\xda\x76\x73\xc4\x59\xc2\xb0\ +\x36\x04\x8a\x62\x18\xb7\x4d\xd7\x9e\xcd\x3a\x04\xec\x5c\x6f\xdb\ +\x46\xa4\x21\x82\x93\xed\x5f\x22\x94\xf2\x6d\xd0\x69\xb6\xa9\xc1\ +\xff\x2d\x48\x7c\x9d\x11\x22\xf8\xba\xe5\x10\x42\x70\xf4\xe4\xf2\ +\xba\x2d\x30\x6e\x9b\xc6\x98\xfa\xf7\x02\xcd\x27\xda\x8f\x30\xac\ +\x8f\xf2\x6e\xf6\x31\x12\x66\x24\x4f\x59\x6d\xb0\x2d\xe1\x56\x36\ +\xd8\x0f\x32\x4c\x04\x65\xad\x5f\x00\xe2\xb6\xd7\x33\xba\x2b\xf0\ +\x7f\xe2\x83\x73\x2e\x33\x4b\xd7\x44\x25\x02\x5b\x6a\x4c\x26\x5b\ +\x17\xed\xa7\xee\xc1\x79\x5a\xfb\xae\x72\x77\xc0\xc9\x98\xe6\xc1\ +\x00\x44\x99\x2c\x24\x46\xa5\x31\x37\x26\x8b\x85\xb1\xcf\x70\xd4\ +\x79\x80\xa6\xbe\xab\x80\xb8\x2d\xa4\xf6\xea\xc9\x65\x0d\x35\x93\ +\x12\x00\xac\xfe\x2d\x6f\x86\x6f\x9f\xab\x1d\x61\x32\x5c\x3c\x69\ +\x7d\x9e\x85\x31\x59\xd8\x2c\x76\x22\x8c\x91\x48\x04\x43\x5e\x0f\ +\x6d\xfd\x8d\xdc\x70\x35\x70\xb9\xbb\x9a\x61\x6d\x08\x21\x38\xea\ +\xf5\x8c\xee\xfa\x65\xb5\xb3\x7f\x3c\xde\x94\x8f\xfe\x9a\xd3\x8b\ +\x0b\xa4\x54\xa5\x80\x3d\x10\x93\xfe\x1b\x3a\x51\xa2\xea\x26\x8a\ +\xe2\xca\x15\xf5\x67\x27\xc3\x99\x92\x20\x60\x45\x55\x39\xf1\xde\ +\x30\x2d\x57\x69\x2a\x4f\x40\x9e\x10\x4a\x82\xa8\x11\xe0\xd0\x95\ +\xa8\xad\x5c\x71\xbd\x7b\xba\xfc\x7f\x01\xe3\xf6\xed\xcb\x2c\x97\ +\xd8\xbf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ " qt_resource_name = "\ @@ -1521,12 +1609,17 @@ qt_resource_name = "\ \x00\x6c\ \x00\x65\x00\x61\x00\x70\x00\x2d\x00\x63\x00\x6f\x00\x6c\x00\x6f\x00\x72\x00\x2d\x00\x73\x00\x6d\x00\x61\x00\x6c\x00\x6c\x00\x2e\ \x00\x70\x00\x6e\x00\x67\ +\x00\x0b\ +\x01\x64\x80\x07\ +\x00\x63\ +\x00\x68\x00\x65\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\ \x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x4e\ +\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x5b\xd7\ \x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xf7\ \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x19\xd2\ diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py new file mode 100644 index 00000000..d04e0f1f --- /dev/null +++ b/src/leap/gui/progress.py @@ -0,0 +1,261 @@ +""" +classes used in progress pages +from first run wizard +""" +try: + from collections import OrderedDict +except ImportError: + # We must be in 2.6 + from leap.util.dicts import OrderedDict +import time + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.baseapp.mainwindow import FunThread + +from leap.gui import mainwindow_rc + + +class ImgWidget(QtGui.QWidget): + + # XXX move to widgets + + def __init__(self, parent=None, img=None): + super(ImgWidget, self).__init__(parent) + self.pic = QtGui.QPixmap(img) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + painter.drawPixmap(0, 0, self.pic) + + +class ProgressStep(object): + """ + Data model for sequential steps + to be used in a progress page in + connection wizard + """ + NAME = 0 + DONE = 1 + + def __init__(self, stepname, done, index=None): + """ + @param step: the name of the step + @type step: str + @param done: whether is completed or not + @type done: bool + """ + self.index = int(index) if index else 0 + self.name = unicode(stepname) + self.done = bool(done) + + @classmethod + def columns(self): + return ('name', 'done') + + +class ProgressStepContainer(object): + """ + a container for ProgressSteps objects + access data in the internal dict + """ + + def __init__(self): + self.dirty = False + self.steps = {} + + def step(self, identity): + return self.step.get(identity) + + def addStep(self, step): + self.steps[step.index] = step + + def removeStep(self, step): + del self.steps[step.index] + del step + self.dirty = True + + def removeAllSteps(self): + for item in iter(self): + self.removeStep(item) + + @property + def columns(self): + return ProgressStep.columns() + + def __len__(self): + return len(self.steps) + + def __iter__(self): + for step in self.steps.values(): + yield step + + +class StepsTableWidget(QtGui.QTableWidget): + """ + initializes a TableWidget + suitable for our display purposes, like removing + header info and grid display + """ + + def __init__(self, parent=None): + super(StepsTableWidget, self).__init__(parent) + + # remove headers and all edit/select behavior + self.horizontalHeader().hide() + self.verticalHeader().hide() + self.setEditTriggers( + QtGui.QAbstractItemView.NoEditTriggers) + self.setSelectionMode( + QtGui.QAbstractItemView.NoSelection) + width = self.width() + # WTF? Here init width is 100... + # but on populating is 456... :( + + # XXX do we need this initial? + print 'init table. width=%s' % width + self.horizontalHeader().resizeSection(0, width * 0.7) + + # this disables the table grid. + # we should add alignment to the ImgWidget (it's top-left now) + self.setShowGrid(False) + + # XXX change image for done to rc + + # Note about the "done" status painting: + # + # XXX currently we are setting the CellWidget + # for the whole table on a per-row basis + # (on add_status_line method on ValidationPage). + # However, a more generic solution might be + # to implement a custom Delegate that overwrites + # the paint method (so it paints a checked tickmark if + # done is True and some other thing if checking or false). + # What we have now is quick and works because + # I'm supposing that on first fail we will + # go back to previous wizard page to signal the failure. + # A more generic solution could be used for + # some failing tests if they are not critical. + + +class ValidationPage(QtGui.QWizardPage): + """ + class to be used as an intermediate + between two pages in a wizard. + shows feedback to the user and goes back if errors, + goes forward if ok. + initializePage triggers a one shot timer + that calls do_checks. + Derived classes should implement + _do_checks and + _do_validation + """ + + # signals + + stepChanged = QtCore.pyqtSignal([str]) + + def __init__(self, parent=None): + super(ValidationPage, self).__init__(parent) + + self.steps = ProgressStepContainer() + self.progress = QtGui.QProgressBar() + + # steps table widget + self.stepsTableWidget = StepsTableWidget(self) + + layout = QtGui.QVBoxLayout() + layout.addWidget(self.progress) + layout.addWidget(self.stepsTableWidget) + + self.setLayout(layout) + self.layout = layout + + self.timer = QtCore.QTimer() + + # connect the new step status + # signal to status handler + self.stepChanged.connect( + self.onStepStatusChanged) + + self.errors = OrderedDict() + + def populateStepsTable(self): + # from examples, + # but I guess it's not needed to re-populate + # the whole table. + table = self.stepsTableWidget + table.setRowCount(len(self.steps)) + columns = self.steps.columns + table.setColumnCount(len(columns)) + + for row, step in enumerate(self.steps): + item = QtGui.QTableWidgetItem(step.name) + item.setData(QtCore.Qt.UserRole, + long(id(step))) + table.setItem(row, columns.index('name'), item) + table.setItem(row, columns.index('done'), + QtGui.QTableWidgetItem(step.done)) + self.resizeTable() + self.update() + + def clearTable(self): + # ??? -- not sure what's the difference + #self.stepsTableWidget.clear() + self.stepsTableWidget.clearContents() + + def resizeTable(self): + # resize first column to ~80% + table = self.stepsTableWidget + FIRST_COLUMN_PERCENT = 0.75 + width = table.width() + print 'populate table. width=%s' % width + table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) + + def onStepStatusChanged(self, status): + if status != "end_sentinel": + self.add_status_line(status) + + def add_status_line(self, message): + print 'adding status line...' + index = len(self.steps) + step = ProgressStep(message, False, index=index) + self.steps.addStep(step) + self.populateStepsTable() + table = self.stepsTableWidget + + # setting cell widget. + # see note on StepsTableWidget about plans to + # change this for a better solution. + + table.setCellWidget( + index - 1, + ProgressStep.DONE, + # XXX pass image in rc + ImgWidget(img=":/images/checked.png")) + + def go_back(self): + self.wizard().back() + + def go_next(self): + self.wizard().next() + + def initializePage(self): + self.steps.removeAllSteps() + self.clearTable() + self.resizeTable() + self.timer.singleShot(0, self.do_checks) + + def do_checks(self): + """ + launches a thread to do the checks + """ + signal = self.stepChanged + self.checks = FunThread( + self._do_checks(signal=signal)) + self.checks.finished.connect(self._do_validation) + self.checks.begin() + print 'check thread started!' + print 'waiting for it to terminate...' + self.checks.wait() diff --git a/src/leap/gui/tests/integration/fake_user_signup.py b/src/leap/gui/tests/integration/fake_user_signup.py index 12f18966..78873749 100644 --- a/src/leap/gui/tests/integration/fake_user_signup.py +++ b/src/leap/gui/tests/integration/fake_user_signup.py @@ -12,6 +12,7 @@ curl -d login=python_test_user -d password_salt=54321\ from BaseHTTPServer import HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler import cgi +import json import urlparse HOST = "localhost" @@ -19,12 +20,15 @@ PORT = 8000 LOGIN_ERROR = """{"errors":{"login":["has already been taken"]}}""" +from leap.base.tests.test_providers import EXPECTED_DEFAULT_CONFIG + class request_handler(BaseHTTPRequestHandler): responses = { '/': ['ok\n'], '/users.json': ['ok\n'], - '/timeout': ['ok\n'] + '/timeout': ['ok\n'], + '/provider.json': ['%s\n' % json.dumps(EXPECTED_DEFAULT_CONFIG)] } def do_GET(self): -- cgit v1.2.3