From c387a52f841e8933ed7282d198ed1ece863979fc Mon Sep 17 00:00:00 2001
From: kali <kali@leap.se>
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.<br><br>Click <i>next</i> 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