summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
authorkali <kali@leap.se>2012-11-28 01:11:05 +0900
committerkali <kali@leap.se>2012-11-28 01:11:05 +0900
commit16f19a225a922dd77f3f6c75c94194ebd229fc67 (patch)
treed77ba10cdad970b5025e75daa2b3be24e35ce0c7 /src/leap
parent862014f68fce37318f77309a8f8f9782dabc60d2 (diff)
parent3ea766452e3c4708c724509d03001c0a0314fcf6 (diff)
Merge branch 'feature/wizard-usability' into develop
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/gui/firstrun/last.py2
-rw-r--r--src/leap/gui/firstrun/login.py244
-rw-r--r--src/leap/gui/firstrun/providerinfo.py178
-rw-r--r--src/leap/gui/firstrun/providerselect.py334
-rw-r--r--src/leap/gui/firstrun/providersetup.py177
-rw-r--r--src/leap/gui/firstrun/register.py260
-rw-r--r--src/leap/gui/firstrun/regvalidation.py179
-rwxr-xr-xsrc/leap/gui/firstrun/wizard.py11
-rw-r--r--src/leap/gui/mainwindow_rc.py367
-rw-r--r--src/leap/gui/progress.py299
-rw-r--r--src/leap/gui/styles.py18
-rw-r--r--src/leap/gui/test_mainwindow_rc.py2
-rw-r--r--src/leap/gui/threads.py8
13 files changed, 1434 insertions, 645 deletions
diff --git a/src/leap/gui/firstrun/last.py b/src/leap/gui/firstrun/last.py
index 13b2f548..d33d2e77 100644
--- a/src/leap/gui/firstrun/last.py
+++ b/src/leap/gui/firstrun/last.py
@@ -78,6 +78,8 @@ class LastPage(QtGui.QWizardPage):
break
except GeneratorExit:
pass
+ except StopIteration:
+ pass
def initializePage(self):
wizard = self.wizard()
diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py
index 4271c774..02bace86 100644
--- a/src/leap/gui/firstrun/login.py
+++ b/src/leap/gui/firstrun/login.py
@@ -4,17 +4,22 @@ LogIn Page, used inf First Run Wizard
from PyQt4 import QtCore
from PyQt4 import QtGui
-#import requests
+import requests
+from leap.base import auth
from leap.gui.firstrun.mixins import UserFormMixIn
+from leap.gui.progress import InlineValidationPage
+from leap.gui import styles
from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX
-from leap.gui.styles import ErrorLabelStyleSheet
-class LogInPage(QtGui.QWizardPage, UserFormMixIn):
+class LogInPage(InlineValidationPage, UserFormMixIn): # InlineValidationPage
+
def __init__(self, parent=None):
+
super(LogInPage, self).__init__(parent)
+ self.current_page = "login"
self.setTitle("Log In")
self.setSubTitle("Log in with your credentials.")
@@ -24,6 +29,12 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(APP_LOGO))
+ self.setupSteps()
+ self.setupUI()
+
+ self.do_confirm_next = False
+
+ def setupUI(self):
userNameLabel = QtGui.QLabel("User &name:")
userNameLineEdit = QtGui.QLineEdit()
userNameLineEdit.cursorPositionChanged.connect(
@@ -34,6 +45,9 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
usernameRe = QtCore.QRegExp(FULL_USERNAME_REGEX)
userNameLineEdit.setValidator(
QtGui.QRegExpValidator(usernameRe, self))
+
+ #userNameLineEdit.setPlaceholderText(
+ #'username@provider.example.org')
self.userNameLineEdit = userNameLineEdit
userPasswordLabel = QtGui.QLabel("&Password:")
@@ -49,7 +63,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
layout.setColumnMinimumWidth(0, 20)
validationMsg = QtGui.QLabel("")
- validationMsg.setStyleSheet(ErrorLabelStyleSheet)
+ validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)
self.validationMsg = validationMsg
layout.addWidget(validationMsg, 0, 3)
@@ -58,18 +72,38 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
layout.addWidget(userPasswordLabel, 2, 0)
layout.addWidget(self.userPasswordLineEdit, 2, 3)
+ # add validation frame
+ self.setupValidationFrame()
+ layout.addWidget(self.valFrame, 4, 2, 4, 2)
+ self.valFrame.hide()
+
+ self.nextText("Log in")
self.setLayout(layout)
#self.registerField('is_login_wizard')
+ def nextText(self, text):
+ self.setButtonText(
+ QtGui.QWizard.NextButton, text)
+
+ def nextFocus(self):
+ self.wizard().button(
+ QtGui.QWizard.NextButton).setFocus()
+
+ def disableNextButton(self):
+ self.wizard().button(
+ QtGui.QWizard.NextButton).setDisabled(True)
+
def onUserNameEdit(self, *args):
if self.initial_username_sample:
self.userNameLineEdit.setText('')
+ # XXX set regular color
self.initial_username_sample = None
- # pagewizard methods
-
- #### begin possible refactor
+ def disableFields(self):
+ for field in (self.userNameLineEdit,
+ self.userPasswordLineEdit):
+ field.setDisabled(True)
def populateErrors(self):
# XXX could move this to ValidationMixin
@@ -77,13 +111,13 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
errors = self.wizard().get_validation_error(
self.current_page)
- prev_er = getattr(self, 'prevalidation_error', None)
+ #prev_er = getattr(self, 'prevalidation_error', None)
showerr = self.validationMsg.setText
- if not errors and prev_er:
- showerr(prev_er)
- return
-
+ #if not errors and prev_er:
+ #showerr(prev_er)
+ #return
+#
if errors:
bad_str = getattr(self, 'bad_string', None)
cur_str = self.userNameLineEdit.text()
@@ -94,13 +128,14 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
self.bad_string = cur_str
showerr(errors)
else:
- if prev_er:
- showerr(prev_er)
- return
+ #if prev_er:
+ #showerr(prev_er)
+ #return
# not the first time
if cur_str == bad_str:
showerr(errors)
else:
+ self.focused_field = False
showerr('')
def cleanup_errormsg(self):
@@ -124,7 +159,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
def set_prevalidation_error(self, error):
self.prevalidation_error = error
- #### end possible refactor
+ # pagewizard methods
def nextId(self):
wizard = self.wizard()
@@ -139,54 +174,157 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn):
def initializePage(self):
super(LogInPage, self).initializePage()
- self.userNameLineEdit.setText('username@provider.example.org')
- self.userNameLineEdit.cursorPositionChanged.connect(
+ username = self.userNameLineEdit
+ username.setText('username@provider.example.org')
+ username.cursorPositionChanged.connect(
self.onUserNameEdit)
self.initial_username_sample = True
+ self.validationMsg.setText('')
+ self.valFrame.hide()
+
+ def reset_validation_status(self):
+ """
+ empty the validation msg
+ and clean the inline validation widget.
+ """
+ self.validationMsg.setText('')
+ self.steps.removeAllSteps()
+ self.clearTable()
def validatePage(self):
- #wizard = self.wizard()
- #eipconfigchecker = wizard.eipconfigchecker()
+ """
+ if not register done, do checks.
+ if done, wait for click.
+ """
+ self.disableNextButton()
+ self.cleanup_errormsg()
+ self.clean_wizard_errors(self.current_page)
+
+ if self.do_confirm_next:
+ full_username = self.userNameLineEdit.text()
+ password = self.userPasswordLineEdit.text()
+ username, domain = full_username.split('@')
+ self.setField('provider_domain', domain)
+ self.setField('login_userName', username)
+ self.setField('login_userPassword', password)
+
+ return True
+
+ if not self.is_done():
+ self.reset_validation_status()
+ self.do_checks()
+
+ return self.is_done()
+
+ def _do_checks(self):
+ # XXX convert this to inline
full_username = self.userNameLineEdit.text()
- password = self.userPasswordLineEdit.text()
- if full_username.count('@') != 1:
- self.set_prevalidation_error(
- "Username must be in the username@provider form.")
- return False
+ ###########################
+ # 0) check user@domain form
+ ###########################
+
+ def checkusername():
+ if full_username.count('@') != 1:
+ return self.fail(
+ self.tr(
+ "Username must be in the username@provider form."))
+ else:
+ return True
+
+ yield(("head_sentinel", 0), checkusername)
+
+ # XXX I think this is not needed
+ # since we're also checking for the is_signup field.
+ #self.wizard().from_login = True
username, domain = full_username.split('@')
- self.setField('provider_domain', domain)
- self.setField('login_userName', username)
- self.setField('login_userPassword', password)
-
- ####################################################
- # Validation logic:
- # move to provider setup page
- ####################################################
+ password = self.userPasswordLineEdit.text()
+
+ # We try a call to an authenticated
+ # page here as a mean to catch
+ # srp authentication errors while
+ wizard = self.wizard()
+ eipconfigchecker = wizard.eipconfigchecker()
+
+ ########################
+ # 1) try name resolution
+ ########################
+ # show the frame before going on...
+ QtCore.QMetaObject.invokeMethod(
+ self, "showStepsFrame")
+
# Able to contact domain?
# can get definition?
# two-by-one
- #try:
- #eipconfigchecker.fetch_definition(domain=domain)
-#
- # we're using requests here for all
- # the possible error cases that it catches.
- #except requests.exceptions.ConnectionError as exc:
- #self.set_validation_status(exc.message[1])
- #return False
- #except requests.exceptions.HTTPError as exc:
- #self.set_validation_status(exc.message)
- #return False
- #wizard.set_providerconfig(
- #eipconfigchecker.defaultprovider.config)
- ####################################################
+ def resolvedomain():
+ try:
+ eipconfigchecker.fetch_definition(domain=domain)
+
+ # we're using requests here for all
+ # the possible error cases that it catches.
+ except requests.exceptions.ConnectionError as exc:
+ return self.fail(exc.message[1])
+ except requests.exceptions.HTTPError as exc:
+ return self.fail(exc.message)
+ except Exception as exc:
+ # XXX get catchall error msg
+ return self.fail(
+ exc.message)
+
+ yield((self.tr("resolving domain name"), 20), resolvedomain)
+
+ wizard.set_providerconfig(
+ eipconfigchecker.defaultprovider.config)
+
+ ########################
+ # 2) do authentication
+ ########################
+ credentials = username, password
+ pCertChecker = wizard.providercertchecker(
+ domain=domain)
+
+ def validate_credentials():
+ #################
+ # FIXME #BUG #638
+ verify = False
+
+ try:
+ pCertChecker.download_new_client_cert(
+ credentials=credentials,
+ verify=verify)
+
+ except auth.SRPAuthenticationError as exc:
+ return self.fail(
+ self.tr("Authentication error: %s" % exc.message))
+
+ except Exception as exc:
+ return self.fail(exc.message)
- # XXX I think this is not needed
- # since we're also checking for the is_signup field.
- self.wizard().from_login = True
+ else:
+ return True
- # some cleanup before we leave the page
- self.cleanup_errormsg()
+ yield(('Validating credentials', 20), validate_credentials)
+
+ self.set_done()
+ yield(("end_sentinel", 0), lambda: None)
+
+ def green_validation_status(self):
+ val = self.validationMsg
+ val.setText(self.tr('Credentials validated.'))
+ val.setStyleSheet(styles.GreenLineEdit)
- return True
+ def on_checks_validation_ready(self):
+ """
+ after checks
+ """
+ if self.is_done():
+ self.disableFields()
+ self.cleanup_errormsg()
+ self.clean_wizard_errors(self.current_page)
+ # make the user confirm the transition
+ # to next page.
+ self.nextText('&Next')
+ self.nextFocus()
+ self.green_validation_status()
+ self.do_confirm_next = True
diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py
index e642fcd0..c5b2984c 100644
--- a/src/leap/gui/firstrun/providerinfo.py
+++ b/src/leap/gui/firstrun/providerinfo.py
@@ -3,42 +3,33 @@ Provider Info Page, used in First run Wizard
"""
import logging
-from PyQt4 import QtCore
from PyQt4 import QtGui
-import requests
-
-from leap.base import exceptions as baseexceptions
-#from leap.crypto import certs
-from leap.eip import exceptions as eipexceptions
-
-from leap.gui.progress import ValidationPage
-from leap.util.web import get_https_domain_and_port
-
-from leap.gui.constants import APP_LOGO, pause_for_user
+from leap.gui.constants import APP_LOGO
logger = logging.getLogger(__name__)
-class ProviderInfoPage(ValidationPage):
+class ProviderInfoPage(QtGui.QWizardPage):
+
def __init__(self, parent=None):
super(ProviderInfoPage, self).__init__(parent)
- self.setTitle("Provider Info")
- #self.setSubTitle("Available information about chosen provider.")
+ self.setTitle(self.tr("Provider Info"))
+ self.setSubTitle(self.tr(
+ "This is what provider says."))
self.setPixmap(
QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(APP_LOGO))
- self.prev_page = "providerselection"
- #self.current_page = "providerinfo"
+ self.create_info_panel()
def create_info_panel(self):
# Use stacked widget instead
# of reparenting the layout.
- self.infoWidget = QtGui.QStackedWidget()
+ infoWidget = QtGui.QStackedWidget()
info = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
@@ -46,22 +37,29 @@ class ProviderInfoPage(ValidationPage):
displayName = QtGui.QLabel("")
description = QtGui.QLabel("")
enrollment_policy = QtGui.QLabel("")
+
# XXX set stylesheet...
# prettify a little bit.
# bigger fonts and so on...
+ # We could use a QFrame here
+
layout.addWidget(displayName)
layout.addWidget(description)
layout.addWidget(enrollment_policy)
layout.addStretch(1)
info.setLayout(layout)
- self.infoWidget.addWidget(info)
+ infoWidget.addWidget(info)
- self.layout.addWidget(self.infoWidget)
+ pageLayout = QtGui.QVBoxLayout()
+ pageLayout.addWidget(infoWidget)
+ self.setLayout(pageLayout)
# add refs to self to allow for
# updates.
+ # Watch out! Have to get rid of these references!
+ # this should be better handled with signals !!
self.displayName = displayName
self.description = description
self.enrollment_policy = enrollment_policy
@@ -76,8 +74,10 @@ class ProviderInfoPage(ValidationPage):
dn = pconfig.get('display_name')
display_name = dn[lang] if dn else ''
+ domain_name = self.field('provider_domain')
+
self.displayName.setText(
- "<b>%s</b>" % display_name)
+ "<b>%s</b> https://%s" % (display_name, domain_name))
desc = pconfig.get('description')
description_text = desc[lang] if desc else ''
@@ -89,142 +89,10 @@ class ProviderInfoPage(ValidationPage):
self.enrollment_policy.setText(
'enrollment policy: %s' % enroll)
- def _do_checks(self, update_signal=None):
- """
- executes actual checks in a separate thread
- """
- def pause_and_finish():
- update_signal.emit("end_sentinel", 100)
- pause_for_user()
-
- wizard = self.wizard()
- prevpage = "providerselection"
-
- full_domain = self.field('provider_domain')
-
- # we check if we have a port in the domain string.
- domain, port = get_https_domain_and_port(full_domain)
- _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain)
-
- netchecker = wizard.netchecker()
- providercertchecker = wizard.providercertchecker()
- eipconfigchecker = wizard.eipconfigchecker(domain=_domain)
-
- update_signal.emit("head_sentinel", 0)
- pause_for_user()
-
- ########################
- # 1) try name resolution
- ########################
- update_signal.emit("Checking that server is reachable", 20)
- logger.debug('checking name resolution')
- try:
- netchecker.check_name_resolution(
- domain)
-
- except baseexceptions.LeapException as exc:
- logger.error(exc.message)
- wizard.set_validation_error(
- prevpage, exc.usermessage)
- pause_and_finish()
- return False
-
- #########################
- # 2) try https connection
- #########################
- update_signal.emit("Checking secure connection to provider", 40)
- logger.debug('checking https connection')
- try:
- providercertchecker.is_https_working(
- "https://%s" % _domain,
- verify=True)
-
- except eipexceptions.HttpsBadCertError as exc:
- logger.debug('exception')
- # XXX skipping for now...
- ##############################################
- # We had this validation logic
- # in the provider selection page before
- ##############################################
- #if self.trustProviderCertCheckBox.isChecked():
- #pass
- #else:
- wizard.set_validation_error(
- prevpage, exc.usermessage)
- #fingerprint = certs.get_cert_fingerprint(
- #domain=domain, sep=" ")
-
- # it's ok if we've trusted this fgprt before
- #trustedcrts = wizard.trusted_certs
- #if trustedcrts and fingerprint.replace(' ', '') in trustedcrts:
- #pass
- #else:
- # let your user face panick :P
- #self.add_cert_info(fingerprint)
- #self.did_cert_check = True
- #self.completeChanged.emit()
- #return False
- pause_and_finish()
- return False
-
- except baseexceptions.LeapException as exc:
- wizard.set_validation_error(
- prevpage, exc.usermessage)
- pause_and_finish()
- return False
-
- ##################################
- # 3) try download provider info...
- ##################################
-
- update_signal.emit("Downloading provider info", 70)
- try:
- # XXX we already set _domain in the initialization
- # so it should not be needed here.
- eipconfigchecker.fetch_definition(domain=_domain)
- wizard.set_providerconfig(
- eipconfigchecker.defaultprovider.config)
- except requests.exceptions.SSLError:
- # XXX we should have catched this before.
- # but cert checking is broken.
- wizard.set_validation_error(
- prevpage,
- "Could not get info from provider.")
- pause_and_finish()
- return False
- except requests.exceptions.ConnectionError:
- wizard.set_validation_error(
- prevpage,
- "Could not download provider info "
- "(refused conn.).")
- pause_and_finish()
- return False
- # XXX catch more errors...
-
- # We're done!
- pause_and_finish()
-
- def _do_validation(self):
- """
- called after _do_checks has finished
- (connected to checker thread finished signal)
- """
- print 'validation...'
- prevpage = "providerselection"
- errors = self.wizard().get_validation_error(prevpage)
-
- if not errors:
- self.progress.hide()
- self.stepsTableWidget.hide()
- self.create_info_panel()
- self.show_provider_info()
-
- else:
- logger.debug('going back with errors')
- logger.debug('ERRORS: %s' % errors)
- self.go_back()
-
def nextId(self):
wizard = self.wizard()
next_ = "providersetupvalidation"
return wizard.get_page_index(next_)
+
+ def initializePage(self):
+ self.show_provider_info()
diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py
index 8d1aa869..3ffc6ff6 100644
--- a/src/leap/gui/firstrun/providerselect.py
+++ b/src/leap/gui/firstrun/providerselect.py
@@ -3,35 +3,69 @@ Select Provider Page, used in First Run Wizard
"""
import logging
+import requests
+
from PyQt4 import QtCore
from PyQt4 import QtGui
-#from leap.base import exceptions as baseexceptions
+from leap.base import exceptions as baseexceptions
#from leap.crypto import certs
-#from leap.eip import exceptions as eipexceptions
+from leap.eip import exceptions as eipexceptions
+from leap.gui.progress import InlineValidationPage
+from leap.gui import styles
+from leap.util.web import get_https_domain_and_port
from leap.gui.constants import APP_LOGO
-from leap.gui.styles import ErrorLabelStyleSheet
logger = logging.getLogger(__name__)
-class SelectProviderPage(QtGui.QWizardPage):
+def delay(obj, method_str):
+ # XXX check newer version in progress.py...
+ """
+ this is a hack to get responsiveness in the ui
+ """
+ QtCore.QTimer().singleShot(
+ 10,
+ lambda: QtCore.QMetaObject.invokeMethod(
+ obj, method_str))
+
+
+class SelectProviderPage(InlineValidationPage):
+
+ #disableCheckButton = QtCore.pyqtSignal()
+ launchChecks = QtCore.pyqtSignal()
+
def __init__(self, parent=None, providers=None):
super(SelectProviderPage, self).__init__(parent)
+ self.current_page = 'providerselection'
- self.setTitle("Enter Provider")
- self.setSubTitle(
+ self.setTitle(self.tr("Enter Provider"))
+ self.setSubTitle(self.tr(
"Please enter the domain of the provider you want "
- "to use for your connection."
+ "to use for your connection.")
)
self.setPixmap(
QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(APP_LOGO))
self.did_cert_check = False
- self.current_page = 'providerselection'
+ self.is_done = False
+
+ self.setupSteps()
+ self.setupUI()
+
+ self.launchChecks.connect(
+ self.launch_checks)
+
+ self.providerNameEdit.editingFinished.connect(
+ lambda: self.providerCheckButton.setFocus(True))
+
+ def setupUI(self):
+ """
+ initializes the UI
+ """
providerNameLabel = QtGui.QLabel("h&ttps://")
# note that we expect the bare domain name
# we will add the scheme later
@@ -59,8 +93,10 @@ class SelectProviderPage(QtGui.QWizardPage):
#self.registerField('provider_name_index', providerNameSelect)
validationMsg = QtGui.QLabel("")
- validationMsg.setStyleSheet(ErrorLabelStyleSheet)
+ validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet)
self.validationMsg = validationMsg
+ providerCheckButton = QtGui.QPushButton(self.tr("chec&k!"))
+ self.providerCheckButton = providerCheckButton
# cert info
@@ -69,7 +105,8 @@ class SelectProviderPage(QtGui.QWizardPage):
# tricky, since the first time came
# from the exception message.
# should get string from exception too!
- self.bad_cert_status = "Server certificate could not be verified."
+ self.bad_cert_status = self.tr(
+ "Server certificate could not be verified.")
self.certInfo = QtGui.QLabel("")
self.certInfo.setWordWrap(True)
@@ -81,25 +118,226 @@ class SelectProviderPage(QtGui.QWizardPage):
self.onTrustCheckChanged)
self.providerNameEdit.textChanged.connect(
self.onProviderChanged)
+ self.providerCheckButton.clicked.connect(
+ self.onCheckButtonClicked)
layout = QtGui.QGridLayout()
layout.addWidget(validationMsg, 0, 2)
layout.addWidget(providerNameLabel, 1, 1)
layout.addWidget(providerNameEdit, 1, 2)
+ layout.addWidget(providerCheckButton, 1, 3)
+
+ # add certinfo group
+ # XXX not shown now. should move to validation box.
+ #layout.addWidget(certinfoGroup, 4, 1, 4, 2)
+ #self.certinfoGroup = certinfoGroup
+ #self.certinfoGroup.hide()
+
+ # add validation frame
+ self.setupValidationFrame()
+ layout.addWidget(self.valFrame, 4, 2, 4, 2)
+ self.valFrame.hide()
- # XXX get a groupbox or something....
- certinfoGroup = QtGui.QGroupBox("Certificate validation")
+ self.setLayout(layout)
+
+ # certinfo
+
+ def setupCertInfoGroup(self):
+ # XXX not used now.
+ certinfoGroup = QtGui.QGroupBox(
+ self.tr("Certificate validation"))
certinfoLayout = QtGui.QVBoxLayout()
certinfoLayout.addWidget(self.certInfo)
certinfoLayout.addWidget(self.certWarning)
certinfoLayout.addWidget(self.trustProviderCertCheckBox)
certinfoGroup.setLayout(certinfoLayout)
+ self.certinfoGroup = self.certinfoGroup
- layout.addWidget(certinfoGroup, 4, 1, 4, 2)
- self.certinfoGroup = certinfoGroup
- self.certinfoGroup.hide()
+ # progress frame
- self.setLayout(layout)
+ def setupValidationFrame(self):
+ qframe = QtGui.QFrame
+ valFrame = qframe()
+ valFrame.setFrameStyle(qframe.NoFrame)
+ valframeLayout = QtGui.QVBoxLayout()
+ zeros = (0, 0, 0, 0)
+ valframeLayout.setContentsMargins(*zeros)
+
+ valframeLayout.addWidget(self.stepsTableWidget)
+ valFrame.setLayout(valframeLayout)
+ self.valFrame = valFrame
+
+ @QtCore.pyqtSlot()
+ def onDisableCheckButton(self):
+ #print 'CHECK BUTTON DISABLED!!!'
+ self.providerCheckButton.setDisabled(True)
+
+ @QtCore.pyqtSlot()
+ def launch_checks(self):
+ self.do_checks()
+
+ def onCheckButtonClicked(self):
+ QtCore.QMetaObject.invokeMethod(
+ self, "onDisableCheckButton")
+
+ QtCore.QMetaObject.invokeMethod(
+ self, "showStepsFrame")
+
+ # is this still needed?
+ # XXX can I doo delay(self, "do_checks") ?
+ delay(self, "launch_checks")
+
+ def _do_checks(self):
+ """
+ generator that yields actual checks
+ that are executed in a separate thread
+ """
+
+ wizard = self.wizard()
+ full_domain = self.providerNameEdit.text()
+
+ # we check if we have a port in the domain string.
+ domain, port = get_https_domain_and_port(full_domain)
+ _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain)
+
+ netchecker = wizard.netchecker()
+
+ providercertchecker = wizard.providercertchecker()
+ eipconfigchecker = wizard.eipconfigchecker(domain=_domain)
+
+ yield(("head_sentinel", 0), lambda: None)
+
+ ########################
+ # 1) try name resolution
+ ########################
+
+ def namecheck():
+ """
+ in which we check if
+ we are able to name resolve
+ this domain
+ """
+ try:
+ netchecker.check_name_resolution(
+ domain)
+
+ except baseexceptions.LeapException as exc:
+ logger.error(exc.message)
+ return self.fail(exc.usermessage)
+
+ except Exception as exc:
+ return self.fail(exc.message)
+
+ else:
+ return True
+
+ logger.debug('checking name resolution')
+ yield((self.tr("checking domain name"), 20), namecheck)
+
+ #########################
+ # 2) try https connection
+ #########################
+
+ def httpscheck():
+ """
+ in which we check
+ if the provider
+ is offering service over
+ https
+ """
+ try:
+ providercertchecker.is_https_working(
+ "https://%s" % _domain,
+ verify=True)
+
+ except eipexceptions.HttpsBadCertError as exc:
+ logger.debug('exception')
+ return self.fail(exc.usermessage)
+ # XXX skipping for now...
+ ##############################################
+ # We had this validation logic
+ # in the provider selection page before
+ ##############################################
+ #if self.trustProviderCertCheckBox.isChecked():
+ #pass
+ #else:
+ #fingerprint = certs.get_cert_fingerprint(
+ #domain=domain, sep=" ")
+
+ # it's ok if we've trusted this fgprt before
+ #trustedcrts = wizard.trusted_certs
+ #if trustedcrts and \
+ # fingerprint.replace(' ', '') in trustedcrts:
+ #pass
+ #else:
+ # let your user face panick :P
+ #self.add_cert_info(fingerprint)
+ #self.did_cert_check = True
+ #self.completeChanged.emit()
+ #return False
+
+ except baseexceptions.LeapException as exc:
+ return self.fail(exc.usermessage)
+
+ except Exception as exc:
+ return self.fail(exc.message)
+
+ else:
+ return True
+
+ logger.debug('checking https connection')
+ yield((self.tr("checking https connection"), 40), httpscheck)
+
+ ##################################
+ # 3) try download provider info...
+ ##################################
+
+ def fetchinfo():
+ try:
+ # XXX we already set _domain in the initialization
+ # so it should not be needed here.
+ eipconfigchecker.fetch_definition(domain=_domain)
+ wizard.set_providerconfig(
+ eipconfigchecker.defaultprovider.config)
+ except requests.exceptions.SSLError:
+ # XXX we should have catched this before.
+ # but cert checking is broken.
+ return self.fail(self.tr(
+ "Could not get info from provider."))
+ except requests.exceptions.ConnectionError:
+ return self.fail(self.tr(
+ "Could not download provider info "
+ "(refused conn.)."))
+
+ except Exception as exc:
+ return self.fail(
+ self.tr(exc.message))
+ else:
+ return True
+
+ yield((self.tr("fetching provider info"), 80), fetchinfo)
+
+ # done!
+
+ self.is_done = True
+ yield(("end_sentinel", 100), lambda: None)
+
+ def on_checks_validation_ready(self):
+ """
+ called after _do_checks has finished.
+ """
+ self.domain_checked = True
+ self.completeChanged.emit()
+ # let's set focus...
+ if self.is_done:
+ self.wizard().clean_validation_error(self.current_page)
+ nextbutton = self.wizard().button(QtGui.QWizard.NextButton)
+ nextbutton.setFocus()
+ else:
+ self.providerNameEdit.setFocus()
+
+ # cert trust verification
+ # (disabled for now)
def is_insecure_cert_trusted(self):
return self.trustProviderCertCheckBox.isChecked()
@@ -117,38 +355,50 @@ class SelectProviderPage(QtGui.QWizardPage):
# trigger signal to redraw next button
self.completeChanged.emit()
+ def add_cert_info(self, certinfo):
+ self.certWarning.setText(
+ "Do you want to <b>trust this provider certificate?</b>")
+ self.certInfo.setText(
+ 'SHA-256 fingerprint: <i>%s</i><br>' % certinfo)
+ self.certInfo.setWordWrap(True)
+ self.certinfoGroup.show()
+
def onProviderChanged(self, text):
+ self.is_done = False
+ provider = self.providerNameEdit.text()
+ if provider:
+ self.providerCheckButton.setDisabled(False)
+ else:
+ self.providerCheckButton.setDisabled(True)
self.completeChanged.emit()
def reset_validation_status(self):
"""
empty the validation msg
+ and clean the inline validation widget.
"""
self.validationMsg.setText('')
-
- #def set_validation_status(selF, STATUS):
- #self.validationMsg.setText(status)
-
- def add_cert_info(self, certinfo):
- self.certWarning.setText(
- "Do you want to <b>trust this provider certificate?</b>")
- self.certInfo.setText(
- 'SHA-256 fingerprint: <i>%s</i><br>' % certinfo)
- self.certInfo.setWordWrap(True)
- self.certinfoGroup.show()
+ self.steps.removeAllSteps()
+ self.clearTable()
+ self.domain_checked = False
# pagewizard methods
def isComplete(self):
provider = self.providerNameEdit.text()
+ if not self.is_done:
+ return False
+
if not provider:
return False
else:
if self.is_insecure_cert_trusted():
return True
if not self.did_cert_check:
- return True
+ if self.is_done:
+ # XXX sure?
+ return True
return False
def populateErrors(self):
@@ -163,17 +413,33 @@ class SelectProviderPage(QtGui.QWizardPage):
bad_str = getattr(self, 'bad_string', None)
cur_str = self.providerNameEdit.text()
showerr = self.validationMsg.setText
+ markred = lambda: self.providerNameEdit.setStyleSheet(
+ styles.ErrorLineEdit)
+ umarkrd = lambda: self.providerNameEdit.setStyleSheet(
+ styles.RegularLineEdit)
if bad_str is None:
# first time we fall here.
# save the current bad_string value
self.bad_string = cur_str
showerr(errors)
+ markred()
else:
# not the first time
+ # XXX hey, this is getting convoluted.
+ # roll out this.
+ # but be careful about all the possibilities
+ # with going back and forth once you
+ # enter a domain.
if cur_str == bad_str:
showerr(errors)
+ markred()
else:
- showerr('')
+ if not getattr(self, 'domain_checked', None):
+ showerr('')
+ umarkrd()
+ else:
+ self.bad_string = cur_str
+ showerr(errors)
def cleanup_errormsg(self):
"""
@@ -181,6 +447,7 @@ class SelectProviderPage(QtGui.QWizardPage):
should be called before leaving the page
"""
self.bad_string = None
+ self.domain_checked = False
def paintEvent(self, event):
"""
@@ -195,7 +462,14 @@ class SelectProviderPage(QtGui.QWizardPage):
def initializePage(self):
self.validationMsg.setText('')
- self.certinfoGroup.hide()
+ if hasattr(self, 'certinfoGroup'):
+ # XXX remove ?
+ self.certinfoGroup.hide()
+ self.is_done = False
+ self.providerCheckButton.setDisabled(True)
+ self.valFrame.hide()
+ self.steps.removeAllSteps()
+ self.clearTable()
def validatePage(self):
# some cleanup before we leave the page
diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py
index 3fb9a19b..1a362794 100644
--- a/src/leap/gui/firstrun/providersetup.py
+++ b/src/leap/gui/firstrun/providersetup.py
@@ -6,10 +6,10 @@ import logging
from PyQt4 import QtGui
-from leap.base import auth
+from leap.base import exceptions as baseexceptions
from leap.gui.progress import ValidationPage
-from leap.gui.constants import APP_LOGO, pause_for_user
+from leap.gui.constants import APP_LOGO
logger = logging.getLogger(__name__)
@@ -17,21 +17,26 @@ logger = logging.getLogger(__name__)
class ProviderSetupValidationPage(ValidationPage):
def __init__(self, parent=None):
super(ProviderSetupValidationPage, self).__init__(parent)
+ self.current_page = "providersetupvalidation"
+
+ # XXX needed anymore?
is_signup = self.field("is_signup")
self.is_signup = is_signup
- self.setTitle("Setting up provider")
- #self.setSubTitle(
- #"auto configuring provider...")
+ self.setTitle(self.tr("Provider setup"))
+ self.setSubTitle(
+ self.tr("Doing autoconfig."))
self.setPixmap(
QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(APP_LOGO))
- def _do_checks(self, update_signal=None):
+ def _do_checks(self):
"""
- executes actual checks in a separate thread
+ generator that yields actual checks
+ that are executed in a separate thread
"""
+
full_domain = self.field('provider_domain')
wizard = self.wizard()
pconfig = wizard.providerconfig
@@ -41,68 +46,50 @@ class ProviderSetupValidationPage(ValidationPage):
pCertChecker = wizard.providercertchecker(
domain=full_domain)
- update_signal.emit('head_sentinel', 0)
-
- ######################################
- if not self.is_signup:
- # We come from login page.
- # We try a call to an authenticated
- # page here as a mean to catch
- # srp authentication errors while
- # we are still at one page's reach
- # of the login credentials input page.
- # (so we're able to go back an correct)
-
- step = "fetch_eipcert"
- update_signal.emit('validating credentials', 20)
+ yield(("head_sentinel", 0), lambda: None)
- unamek = 'login_userName'
- passwk = 'login_userPassword'
+ ########################
+ # 1) fetch ca cert
+ ########################
- username = self.field(unamek)
- password = self.field(passwk)
- credentials = username, password
-
- #################
- # FIXME #BUG #638
- verify = False
+ def fetchcacert():
+ if pconfig:
+ ca_cert_uri = pconfig.get('ca_cert_uri').geturl()
+ else:
+ ca_cert_uri = None
+ # XXX check scheme == "https"
+ # XXX passing verify == False because
+ # we have trusted right before.
+ # We should check it's the same domain!!!
+ # (Check with the trusted fingerprints dict
+ # or something smart)
try:
- pCertChecker.download_new_client_cert(
- credentials=credentials,
- verify=verify)
-
- except auth.SRPAuthenticationError as exc:
- self.set_error(
- step,
- "Authentication error: %s" % exc.message)
- return False
-
- pause_for_user()
+ pCertChecker.download_ca_cert(
+ uri=ca_cert_uri,
+ verify=False)
- #######################################
+ except baseexceptions.LeapException as exc:
+ logger.error(exc.message)
+ # XXX this should be _ method
+ return self.fail(self.tr(exc.usermessage))
- update_signal.emit('Fetching CA certificate', 30)
- pause_for_user()
+ except Exception as exc:
+ return self.fail(exc.message)
- if pconfig:
- ca_cert_uri = pconfig.get('ca_cert_uri').geturl()
- else:
- ca_cert_uri = None
+ else:
+ return True
- # XXX check scheme == "https"
- # XXX passing verify == False because
- # we have trusted right before.
- # We should check it's the same domain!!!
- # (Check with the trusted fingerprints dict
- # or something smart)
+ yield((self.tr('Fetching CA certificate'), 30),
+ fetchcacert)
- pCertChecker.download_ca_cert(
- uri=ca_cert_uri,
- verify=False)
- pause_for_user()
+ #########################
+ # 2) check CA fingerprint
+ #########################
- update_signal.emit('Checking CA fingerprint', 66)
+ def checkcafingerprint():
+ # XXX get the real thing!!!
+ pass
#ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None)
# XXX get fingerprint dict (types)
@@ -115,31 +102,41 @@ class ProviderSetupValidationPage(ValidationPage):
# should catch exception
#return False
- update_signal.emit('Validating api certificate', 90)
-
- #api_uri = pconfig.get('api_uri', None)
- #try:
- #api_cert_verified = pCertChecker.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
-
- #if not api_cert_verified:
- # XXX update validationMsg
- # should catch exception
- #return False
- pause_for_user()
- #ca_cert_path = checker.ca_cert_path
-
- update_signal.emit('end_sentinel', 100)
- pause_for_user()
-
- def _do_validation(self):
+ yield((self.tr("Checking CA fingerprint"), 60),
+ checkcafingerprint)
+
+ #########################
+ # 2) check CA fingerprint
+ #########################
+
+ def validatecacert():
+ pass
+ #api_uri = pconfig.get('api_uri', None)
+ #try:
+ #api_cert_verified = pCertChecker.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
+
+ #if not api_cert_verified:
+ # XXX update validationMsg
+ # should catch exception
+ #return False
+
+ #???
+ #ca_cert_path = checker.ca_cert_path
+
+ yield((self.tr('Validating api certificate'), 90), validatecacert)
+
+ self.set_done()
+ yield(('end_sentinel', 100), lambda: None)
+
+ def on_checks_validation_ready(self):
"""
called after _do_checks has finished
(connected to checker thread finished signal)
@@ -153,10 +150,11 @@ class ProviderSetupValidationPage(ValidationPage):
wizard.set_validation_error(
prevpage,
first_error)
- self.go_back()
+ # XXX don't go back, signal error
+ #self.go_back()
else:
- logger.debug('going next')
- self.go_next()
+ logger.debug('should be going next, wait on user')
+ #self.go_next()
def nextId(self):
wizard = self.wizard()
@@ -169,3 +167,8 @@ class ProviderSetupValidationPage(ValidationPage):
# XXX bad name. change to connect again.
next_ = 'signupvalidation'
return wizard.get_page_index(next_)
+
+ def initializePage(self):
+ super(ProviderSetupValidationPage, self).initializePage()
+ self.set_undone()
+ self.completeChanged.emit()
diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py
index b46dd4cd..e85723cb 100644
--- a/src/leap/gui/firstrun/register.py
+++ b/src/leap/gui/firstrun/register.py
@@ -1,8 +1,11 @@
"""
Register User Page, used in First Run Wizard
"""
+import json
import logging
+import socket
+import requests
from PyQt4 import QtCore
from PyQt4 import QtGui
@@ -11,24 +14,37 @@ from leap.gui.firstrun.mixins import UserFormMixIn
logger = logging.getLogger(__name__)
+from leap.base import auth
+from leap.gui import styles
from leap.gui.constants import APP_LOGO, BARE_USERNAME_REGEX
+from leap.gui.progress import InlineValidationPage
from leap.gui.styles import ErrorLabelStyleSheet
-class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
+class RegisterUserPage(InlineValidationPage, UserFormMixIn):
def __init__(self, parent=None):
super(RegisterUserPage, self).__init__(parent)
+ self.current_page = "signup"
- self.setTitle("Sign Up")
+ self.setTitle(self.tr("Sign Up"))
+ # subtitle is set in the initializePage
self.setPixmap(
QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(APP_LOGO))
- self.current_page = "signup"
+ # commit page means there's no way back after this...
+ # XXX should change the text on the "commit" button...
+ self.setCommitPage(True)
+
+ self.setupSteps()
+ self.setupUI()
+ self.do_confirm_next = False
+ self.focused_field = False
+ def setupUI(self):
userNameLabel = QtGui.QLabel("User &name:")
userNameLineEdit = QtGui.QLineEdit()
userNameLineEdit.cursorPositionChanged.connect(
@@ -59,6 +75,7 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
self.registerField('userName*', self.userNameLineEdit)
self.registerField('userPassword*', self.userPasswordLineEdit)
+ self.registerField('userPassword2*', self.userPassword2LineEdit)
# XXX missing password confirmation
# XXX validator!
@@ -81,21 +98,72 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
layout.addWidget(self.userPasswordLineEdit, 2, 3)
layout.addWidget(self.userPassword2LineEdit, 3, 3)
layout.addWidget(rememberPasswordCheckBox, 4, 3, 4, 4)
+
+ # add validation frame
+ self.setupValidationFrame()
+ layout.addWidget(self.valFrame, 5, 2, 5, 2)
+ self.valFrame.hide()
+
self.setLayout(layout)
+ self.commitText("Sign up!")
- # pagewizard methods
+ # commit button
+
+ def commitText(self, text):
+ # change "commit" button text
+ self.setButtonText(
+ QtGui.QWizard.CommitButton, text)
+
+ @property
+ def commitButton(self):
+ return self.wizard().button(QtGui.QWizard.CommitButton)
+
+ def commitFocus(self):
+ self.commitButton.setFocus()
+
+ def disableCommitButton(self):
+ self.commitButton.setDisabled(True)
+
+ def disableFields(self):
+ for field in (self.userNameLineEdit,
+ self.userPasswordLineEdit,
+ self.userPassword2LineEdit):
+ field.setDisabled(True)
+
+ # error painting
+
+ def markRedAndGetFocus(self, field):
+ field.setStyleSheet(styles.ErrorLineEdit)
+ if not self.focused_field:
+ self.focused_field = True
+ field.setFocus(QtCore.Qt.OtherFocusReason)
+
+ def markRegular(self, field):
+ field.setStyleSheet(styles.RegularLineEdit)
def populateErrors(self):
- # XXX could move this to ValidationMixin
- # used in providerselect too
+ def showerr(text):
+ self.validationMsg.setText(text)
+ err_lower = text.lower()
+ if "username" in err_lower:
+ self.markRedAndGetFocus(
+ self.userNameLineEdit)
+ if "password" in err_lower:
+ self.markRedAndGetFocus(
+ self.userPasswordLineEdit)
+
+ def unmarkred():
+ for field in (self.userNameLineEdit,
+ self.userPasswordLineEdit,
+ self.userPassword2LineEdit):
+ self.markRegular(field)
errors = self.wizard().get_validation_error(
self.current_page)
if errors:
bad_str = getattr(self, 'bad_string', None)
cur_str = self.userNameLineEdit.text()
- showerr = self.validationMsg.setText
- prev_er = getattr(self, 'prevalidation_error', None)
+ #prev_er = getattr(self, 'prevalidation_error', None)
if bad_str is None:
# first time we fall here.
@@ -103,14 +171,20 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
self.bad_string = cur_str
showerr(errors)
else:
- if prev_er:
- showerr(prev_er)
- return
+ #if prev_er:
+ #showerr(prev_er)
+ #return
# not the first time
if cur_str == bad_str:
showerr(errors)
else:
+ self.focused_field = False
showerr('')
+ unmarkred()
+ else:
+ # no errors
+ self.focused_field = False
+ unmarkred()
def cleanup_errormsg(self):
"""
@@ -130,45 +204,149 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
super(RegisterUserPage, self).paintEvent(event)
self.populateErrors()
- def set_prevalidation_error(self, error):
- self.prevalidation_error = error
-
- def validatePage(self):
+ def _do_checks(self):
"""
- we only pre-validate here password weakness
- stuff, or any other client side validation
- that we think of.
- real server validation is made on next page,
- and if any errors are thrown there we come back
- and re-display the validation label.
+ generator that yields actual checks
+ that are executed in a separate thread
"""
-
- #username = self.userNameLineEdit.text()
+ provider = self.field('provider_domain')
+ username = self.userNameLineEdit.text()
password = self.userPasswordLineEdit.text()
password2 = self.userPassword2LineEdit.text()
- # we better have here
- # some call to a password checker...
- # to assess strenght and avoid silly stuff.
+ def checkpass():
+ # we better have here
+ # some call to a password checker...
+ # to assess strenght and avoid silly stuff.
+
+ if password != password2:
+ return self.fail(self.tr('Password does not match..'))
+
+ if len(password) < 6:
+ #self.set_prevalidation_error('Password too short.')
+ return self.fail(self.tr('Password too short.'))
+
+ if password == "123456":
+ # joking, but not too much.
+ #self.set_prevalidation_error('Password too obvious.')
+ return self.fail(self.tr('Password too obvious.'))
+
+ # go
+ return True
+
+ yield(("head_sentinel", 0), checkpass)
- if password != password2:
- self.set_prevalidation_error('Password does not match.')
- return False
+ # XXX should emit signal for .show the frame!
+ # XXX HERE!
- if len(password) < 6:
- self.set_prevalidation_error('Password too short.')
- return False
+ ##################################################
+ # 1) register user
+ ##################################################
- if password == "123456":
- # joking, but not too much.
- self.set_prevalidation_error('Password too obvious.')
- return False
+ # show the frame before going on...
+ QtCore.QMetaObject.invokeMethod(
+ self, "showStepsFrame")
- # some cleanup before we leave the page
+ def register():
+ # XXX FIXME!
+ verify = False
+
+ signup = auth.LeapSRPRegister(
+ schema="https",
+ provider=provider,
+ verify=verify)
+ try:
+ ok, req = signup.register_user(
+ username, password)
+
+ except socket.timeout:
+ return self.fail(
+ self.tr("Error connecting to provider (timeout)"))
+
+ except requests.exceptions.ConnectionError as exc:
+ logger.error(exc.message)
+ return self.fail(
+ self.tr('Error Connecting to provider (connerr).'))
+ except Exception as exc:
+ return self.fail(exc.message)
+
+ # XXX check for != OK instead???
+
+ if req.status_code in (404, 500):
+ return self.fail(
+ self.tr(
+ "Error during registration (%s)") % req.status_code)
+
+ validation_msgs = json.loads(req.content)
+ errors = validation_msgs.get('errors', None)
+ logger.debug('validation errors: %s' % validation_msgs)
+
+ if errors and errors.get('login', None):
+ # XXX this sometimes catch the blank username
+ # but we're not allowing that (soon)
+ return self.fail(
+ self.tr('Username not available.'))
+
+ logger.debug('registering user')
+ yield(("registering with provider", 40), register)
+
+ self.set_done()
+ yield(("end_sentinel", 0), lambda: None)
+
+ def on_checks_validation_ready(self):
+ """
+ after checks
+ """
+ if self.is_done():
+ self.disableFields()
+ self.cleanup_errormsg()
+ self.clean_wizard_errors(self.current_page)
+ # make the user confirm the transition
+ # to next page.
+ self.commitText('Connect!')
+ self.commitFocus()
+ self.green_validation_status()
+ self.do_confirm_next = True
+
+ def green_validation_status(self):
+ val = self.validationMsg
+ val.setText(self.tr('Registration succeeded!'))
+ val.setStyleSheet(styles.GreenLineEdit)
+
+ def reset_validation_status(self):
+ """
+ empty the validation msg
+ and clean the inline validation widget.
+ """
+ self.validationMsg.setText('')
+ self.steps.removeAllSteps()
+ self.clearTable()
+
+ # pagewizard methods
+
+ def validatePage(self):
+ """
+ if not register done, do checks.
+ if done, wait for click.
+ """
+ self.disableCommitButton()
self.cleanup_errormsg()
+ self.clean_wizard_errors(self.current_page)
+
+ # After a successful validation
+ # (ie, success register with server)
+ # we change the commit button text
+ # and set this flag to True.
+ if self.do_confirm_next:
+ return True
+
+ if not self.is_done():
+ # calls checks, which after successful
+ # execution will call on_checks_validation_ready
+ self.reset_validation_status()
+ self.do_checks()
- # go
- return True
+ return self.is_done()
def initializePage(self):
"""
@@ -176,13 +354,15 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn):
"""
provider = self.field('provider_domain')
self.setSubTitle(
- "Register a new user with provider %s." %
+ self.tr("Register a new user with provider %s.") %
provider)
self.validationMsg.setText('')
self.userPassword2LineEdit.setText('')
+ self.valFrame.hide()
def nextId(self):
wizard = self.wizard()
if not wizard:
return
+ # XXX this should be called connect
return wizard.get_page_index('signupvalidation')
diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py
index dbe30d3c..0e67834b 100644
--- a/src/leap/gui/firstrun/regvalidation.py
+++ b/src/leap/gui/firstrun/regvalidation.py
@@ -9,18 +9,18 @@ used in First Run Wizard
# the login branch of the wizard.
import logging
-import json
-import socket
+#import json
+#import socket
from PyQt4 import QtGui
-import requests
+#import requests
from leap.gui.progress import ValidationPage
from leap.util.web import get_https_domain_and_port
from leap.base import auth
-from leap.gui.constants import APP_LOGO, pause_for_user
+from leap.gui.constants import APP_LOGO
logger = logging.getLogger(__name__)
@@ -29,16 +29,10 @@ class RegisterUserValidationPage(ValidationPage):
def __init__(self, parent=None):
super(RegisterUserValidationPage, self).__init__(parent)
- is_signup = self.field("is_signup")
- self.is_signup = is_signup
- if is_signup:
- title = "User Creation"
- subtitle = "Registering account with provider."
- else:
- title = "Connecting..."
- # XXX uh... really?
- subtitle = "Checking connection with provider."
+ title = "Connecting..."
+ # XXX uh... really?
+ subtitle = "Checking connection with provider."
self.setTitle(title)
self.setSubTitle(subtitle)
@@ -67,7 +61,7 @@ class RegisterUserValidationPage(ValidationPage):
# Set Credentials.
# username and password are in different fields
# if they were stored in log_in or sign_up pages.
- is_signup = self.is_signup
+ is_signup = self.field("is_signup")
unamek_base = 'userName'
passwk_base = 'userPassword'
@@ -83,125 +77,58 @@ class RegisterUserValidationPage(ValidationPage):
pCertChecker = wizard.providercertchecker(
domain=full_domain)
- ###########################################
- # only if from signup
- if is_signup:
- signup = auth.LeapSRPRegister(
- schema="https",
- provider=full_domain,
- verify=verify)
-
- update_signal.emit("head_sentinel", 0)
+ yield(("head_sentinel", 0), lambda: None)
##################################################
- # 1) register user
+ # 1) fetching eip service config
##################################################
- # only if from signup.
-
- if is_signup:
-
- step = "register"
- update_signal.emit("checking availability", 20)
- update_signal.emit("registering with provider", 40)
- logger.debug('registering user')
-
+ def fetcheipconf():
try:
- ok, req = signup.register_user(
- username, password)
-
- except socket.timeout:
- self.set_error(
- step,
- "Error connecting to provider (timeout)")
- pause_for_user()
- return False
-
- except requests.exceptions.ConnectionError as exc:
- logger.error(exc.message)
- self.set_error(
- step,
- "Error connecting to provider "
- "(connection error)")
- # XXX we should signal a BAD step
- pause_for_user()
- update_signal.emit("connection error!", 50)
- pause_for_user()
- return False
-
- # XXX check for != OK instead???
-
- if req.status_code in (404, 500):
- self.set_error(
- step,
- "Error during registration (%s)" % req.status_code)
- pause_for_user()
- return False
-
- validation_msgs = json.loads(req.content)
- errors = validation_msgs.get('errors', None)
- logger.debug('validation errors: %s' % validation_msgs)
-
- if errors and errors.get('login', None):
- # XXX this sometimes catch the blank username
- # but we're not allowing that (soon)
- self.set_error(
- step,
- 'Username not available.')
- pause_for_user()
- return False
-
- pause_for_user()
+ eipconfigchecker.fetch_eip_service_config(
+ domain=full_domain)
- ##################################################
- # 2) fetching eip service config
- ##################################################
+ # XXX get specific exception
+ except Exception as exc:
+ return self.fail(exc.message)
- step = "fetch_eipconf"
- fetching_eipconf_msg = "Fetching eip service configuration"
- update_signal.emit(fetching_eipconf_msg, 60)
- try:
- eipconfigchecker.fetch_eip_service_config(
- domain=full_domain)
-
- # XXX get specific exception
- except:
- self.set_error(
- step,
- 'Could not download eip config.')
- pause_for_user()
- return False
- pause_for_user()
+ yield((self.tr("Fetching provider config..."), 40),
+ fetcheipconf)
##################################################
- # 3) getting client certificate
+ # 2) getting client certificate
##################################################
- # XXX maybe only do this if we come from signup
-
- step = "fetch_eipcert"
- fetching_clientcert_msg = "Fetching eip certificate"
- update_signal.emit(fetching_clientcert_msg, 80)
- try:
- pCertChecker.download_new_client_cert(
- credentials=credentials,
- verify=verify)
+ def fetcheipcert():
+ try:
+ pCertChecker.download_new_client_cert(
+ credentials=credentials,
+ verify=verify)
- except auth.SRPAuthenticationError as exc:
- self.set_error(
- step,
- "Authentication error: %s" % exc.message)
- return False
+ except auth.SRPAuthenticationError as exc:
+ return self.fail(self.tr(
+ "Authentication error: %s" % exc.message))
+ else:
+ return True
- pause_for_user()
+ yield((self.tr("Fetching eip certificate"), 80),
+ fetcheipcert)
################
# end !
################
+ self.set_done()
+ yield(("end_sentinel", 100), lambda: None)
- update_signal.emit("end_sentinel", 100)
- pause_for_user()
-
+ def on_checks_validation_ready(self):
+ """
+ called after _do_checks has finished
+ (connected to checker thread finished signal)
+ """
+ # this should be called CONNECT PAGE AGAIN.
# here we go! :)
+ full_domain = self.field('provider_domain')
+ domain, port = get_https_domain_and_port(full_domain)
+ _domain = u"%s:%s" % (domain, port) if port != 443 else unicode(domain)
self.run_eip_checks_for_provider_and_connect(_domain)
def run_eip_checks_for_provider_and_connect(self, domain):
@@ -225,6 +152,14 @@ class RegisterUserValidationPage(ValidationPage):
"probably the wizard has been launched "
"in an stand-alone way.")
+ # XXX look for a better place to signal
+ # we are done.
+ # We could probably have a fake validatePage
+ # that checks if the domain transfer has been
+ # done to conductor object, triggers the start_signal
+ # and does the go_next()
+ self.set_done()
+
def eip_error_check(self):
"""
a version of the main app error checker,
@@ -241,7 +176,8 @@ class RegisterUserValidationPage(ValidationPage):
called after _do_checks has finished
(connected to checker thread finished signal)
"""
- prevpage = "signup" if self.is_signup else "login"
+ is_signup = self.field("is_signup")
+ prevpage = "signup" if is_signup else "login"
wizard = self.wizard()
if self.errors:
@@ -253,13 +189,16 @@ class RegisterUserValidationPage(ValidationPage):
first_error)
self.go_back()
else:
- logger.debug('going next')
- # check if this "next" interferes
- # with the eip signal.
- self.go_next()
+ logger.debug('should go next, wait for user to click next')
+ #self.go_next()
def nextId(self):
wizard = self.wizard()
if not wizard:
return
return wizard.get_page_index('lastpage')
+
+ def initializePage(self):
+ super(RegisterUserValidationPage, self).initializePage()
+ self.set_undone()
+ self.completeChanged.emit()
diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py
index bbb48149..9b77b877 100755
--- a/src/leap/gui/firstrun/wizard.py
+++ b/src/leap/gui/firstrun/wizard.py
@@ -39,7 +39,7 @@ TODO-ish:
[ ] Document signals used / expected.
[ ] Separate style from widgets.
[ ] Fix TOFU Widget for provider cert.
-[ ] Refactor widgets out.
+[X] Refactor widgets out.
[ ] Follow more MVC style.
[ ] Maybe separate "first run wizard" into different wizards
that share some of the pages?
@@ -137,6 +137,10 @@ class FirstRunWizard(QtGui.QWizard):
QtGui.QWizard.BackgroundPixmap,
QtGui.QPixmap(':/images/background.png'))
+ # set options
+ self.setOption(QtGui.QWizard.IndependentPages, on=False)
+ self.setOption(QtGui.QWizard.NoBackButtonOnStartPage, on=True)
+
self.setWindowTitle("First Run Wizard")
# TODO: set style for MAC / windows ...
@@ -167,6 +171,11 @@ class FirstRunWizard(QtGui.QWizard):
def set_validation_error(self, pagename, error):
self.validation_errors[pagename] = error
+ def clean_validation_error(self, pagename):
+ vald = self.validation_errors
+ if pagename in vald:
+ del vald[pagename]
+
def get_validation_error(self, pagename):
return self.validation_errors.get(pagename, None)
diff --git a/src/leap/gui/mainwindow_rc.py b/src/leap/gui/mainwindow_rc.py
index 63e9f6be..5bee35c7 100644
--- a/src/leap/gui/mainwindow_rc.py
+++ b/src/leap/gui/mainwindow_rc.py
@@ -2,7 +2,7 @@
# Resource object code
#
-# Created: Tue Nov 6 01:22:11 2012
+# Created: Wed Nov 21 04:25:36 2012
# by: The Resource Compiler for PyQt (Qt v4.8.2)
#
# WARNING! All changes made in this file will be lost!
@@ -236,6 +236,87 @@ qt_resource_data = "\
\x71\xa4\x40\xda\x14\x7a\xd1\x73\x1f\xf4\x7f\xb7\xf9\x1f\xc2\x26\
\x56\xd5\x70\x45\xfc\x8a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\
\x60\x82\
+\x00\x00\x04\xec\
+\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\x06\xec\x00\x00\x06\xec\
+\x01\x1e\x75\x38\x35\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\x00\x13\x74\x45\
+\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\
+\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x4a\x49\x44\x41\
+\x54\x48\x89\x8d\x96\x5d\x6c\x53\x65\x18\xc7\x7f\xef\x39\x6b\xbb\
+\x7e\x9c\x75\x65\xad\x2b\x9b\xfb\xd0\x31\xdd\x14\xb6\x8c\x19\x44\
+\x90\x44\x63\x82\x42\x88\x5e\x90\x98\xcc\x19\x15\x13\xd4\x18\x76\
+\x61\xd4\x18\xe3\x85\x57\xca\x05\xe1\xc2\x0c\xa3\xa8\x51\xd0\x4c\
+\x12\xe3\x85\x31\x80\x26\x6a\xe2\x85\x23\xb0\x38\xb6\xc1\x1c\xce\
+\xb1\x40\x59\xf6\xe5\xca\xda\xae\xed\xfa\x75\x7a\x5e\x2f\x4e\xd7\
+\x59\xd6\x32\xfe\xc9\x7b\xf3\x9e\xe7\xf9\xff\x9f\xe7\xff\x9e\xf3\
+\x9c\x57\x48\x29\x59\x0f\xbd\x7b\x85\x0d\x17\xed\x1e\xbb\xb2\x07\
+\x20\x94\x30\x7e\x22\xc6\x48\xcf\x59\x99\x5a\x2f\x57\x94\x12\xf8\
+\xec\x55\x61\x71\x65\x6d\x47\xfc\xbe\xda\x47\x9d\x5a\xa5\xbf\xda\
+\x69\xaf\xda\xe0\x28\x2f\x07\x58\x5c\x4e\x26\xe7\xe3\x89\x9b\xf1\
+\x68\x78\x6e\x6e\x61\xfa\x8f\x98\x9a\x7a\xfb\x95\xe3\x32\x73\xc7\
+\x02\x9f\x76\x89\x8e\xba\xda\xda\x2f\xb7\x37\xdf\xdf\xe6\x2a\x13\
+\x8a\x94\x06\x82\xc2\x38\x89\x40\x08\x85\x98\x2e\x8d\xf3\x13\xe3\
+\x97\xa6\xa6\xa7\x5f\x7e\xed\x94\x1c\x5a\x57\xa0\xef\xa0\xfd\x70\
+\x5b\xf3\x96\x03\xcd\xde\x8a\x6a\x61\x64\xd7\x73\xc0\x14\x53\x54\
+\x26\x82\x4b\xf3\x97\x26\x2e\x7f\xd5\xfd\x79\xe2\xdd\x92\x02\x27\
+\x5f\x2a\x7b\xe1\x89\xce\x1d\xc7\xbc\x76\x55\x13\xc5\x98\xac\x4e\
+\x10\x0a\xa4\xa2\x6b\x45\x80\x60\x22\x1b\xfd\x6d\xf0\xdc\xa1\x17\
+\x4f\xe8\x5f\xaf\x11\x38\xfa\x9c\xf0\x6e\xdb\xf4\xc0\xf9\x6d\xf5\
+\xfe\x26\x30\xf2\x89\xca\xc6\x76\xd4\x07\xf7\xa3\xd4\x74\x80\xd5\
+\x65\x6e\xa6\xe3\x64\x03\xfd\x64\x2f\x9e\x40\x46\x67\xff\x27\xa3\
+\x30\x70\x63\x6e\x72\xe0\xea\xd8\xf6\x37\xbf\x95\x41\x73\x27\x87\
+\x06\x8f\xa7\x6f\x6b\x7d\x4d\x01\x39\x80\x52\xff\x08\x4a\xe3\xae\
+\x55\xf2\x5c\x27\x6a\xf3\x6e\x2c\x7b\x8f\x9a\x5d\xe5\x61\xb0\xb5\
+\xbe\xa6\xa9\xc1\xe3\xe9\x5b\x95\x04\x7a\xbb\x44\x47\x5b\x53\xcb\
+\x4e\x15\xbd\x98\x31\xc8\x70\x00\xfd\xfc\xc7\x64\xce\xbc\x81\x7e\
+\xe1\x13\xc8\x75\x2d\xb4\x8d\x28\xb5\x0f\x15\xc4\xaa\xe8\xb4\x35\
+\xb5\xec\xec\xed\x12\x1d\x00\x65\x00\xee\x72\x65\x9f\x5f\x73\x38\
+\x05\x6b\x0f\x35\x3b\xf6\x03\xfa\xc0\xf1\x3c\x29\xb3\xc3\xa8\xf7\
+\x3e\x8e\xf0\xb5\x98\x22\xf6\x0d\x05\xf1\x02\xf0\x6b\x0e\xa7\xbb\
+\x5c\xd9\x07\x0c\x29\x00\x9a\xc3\xd5\x69\x55\xd5\xe2\xd5\x47\xe7\
+\x56\xc9\x01\xe1\xbe\x1b\xe1\xb9\x67\xf5\x79\x70\x7c\x4d\x8e\x55\
+\x55\xd1\x1c\xae\xce\xbc\x45\x15\x6e\x5f\x9d\x90\xc5\xed\x29\xa8\
+\xae\xa2\x06\xcb\x53\x47\xa0\xcc\x66\x76\x37\xfa\x3d\xd9\xa9\x81\
+\xb5\x71\x52\xa7\xc2\xed\xab\x83\x9c\x45\x76\xbb\x56\x25\xa5\xa4\
+\xe8\xab\xb9\x02\x9b\x86\x65\xf7\x87\x08\xcd\x6f\x92\x8f\x9f\x21\
+\xf5\xdd\xf3\xa0\xa7\x10\xe5\x6e\x44\x45\x2d\x38\x7d\x08\x21\x90\
+\xd2\xe4\xcc\x0b\x24\x12\xd1\x9b\x42\xbd\xab\x81\x6c\xba\x28\xb7\
+\x94\x06\x65\xcd\x4f\x22\x2a\x1b\x00\x30\xa6\xff\x24\xd5\xb7\x1f\
+\x74\x73\x14\xc9\x64\x04\x99\x8c\x80\xc5\x8e\xe2\xae\x03\xab\x93\
+\x44\x22\x7a\x33\x6f\xd1\x52\x64\x61\x0a\xb5\xbc\x28\xb1\xb1\x34\
+\x83\x91\xb3\xc1\x98\x1d\xc1\x98\x1d\x41\x3f\xd7\x9b\x27\x2f\x40\
+\x26\x81\x11\xfc\x07\x99\x8a\x99\x9c\x2b\x1d\x44\x97\x63\x83\xc9\
+\xe8\xfc\x33\x36\x23\x05\xaa\x05\xd2\xcb\xc8\x74\xcc\xfc\x88\x72\
+\x5d\xa5\x7f\x3c\x74\x3b\x03\x0b\x90\x52\xed\x44\x97\x63\x83\x79\
+\x81\x48\xd2\x38\x3d\x1b\xcf\xbc\x53\x1f\xb9\xe4\x44\x1a\x45\x93\
+\xac\xcf\x7e\x83\xda\xb8\xcb\x2c\xf4\xd7\xf7\xd1\x2f\x9e\x2c\xce\
+\x2e\x14\xe6\xd2\x65\xf1\x48\xd2\x38\x0d\x39\x8b\x7a\x4e\xc9\xa1\
+\xd1\xc0\xb5\xfe\xac\xb7\xb5\x64\x55\xc2\xe5\x47\x54\x36\x98\xe7\
+\x60\xd3\x4a\xc6\x65\xbd\xad\x8c\x06\xae\xf5\xf7\xe4\x26\x6b\x7e\
+\x54\x04\x42\xa1\xee\xe1\x90\x31\x29\x1c\xde\xd2\xbd\xaf\x03\xe1\
+\xf0\x32\x1c\x32\x26\x03\xa1\x50\x77\x7e\xef\xd6\x69\xfa\x58\x7b\
+\xe7\x31\x5f\x78\x54\x23\xb3\x5c\x90\xac\xf8\xdb\x10\x0e\xf3\xab\
+\x35\x82\x13\xc8\xa5\xe9\x42\x76\x8b\x83\x85\xca\xcd\xd1\xdf\x47\
+\x06\x8b\x4f\xd3\x15\xf4\x1d\xb4\x1f\xde\xd2\xd4\x7a\x60\x93\x1a\
+\xaa\x26\x74\xfd\xce\x4a\xf7\x34\x72\x35\xeb\x99\xbf\x3c\x79\xe5\
+\xf6\xff\x83\x15\x7c\xf0\xb4\xd8\xbe\xb9\xa9\xe6\x8b\x1d\x0d\xd5\
+\xad\xae\xd8\x94\x22\x13\x21\x90\xb7\xcc\x29\xa1\x22\xec\x1e\x62\
+\xae\x3a\xa3\xff\xfa\xfc\xdf\xe7\xc6\x66\x5e\x3f\xf2\x0b\xfd\x52\
+\x16\x8e\x84\x02\x01\x21\x84\x0a\x54\x01\x95\x9a\x1d\xdf\x7b\x7b\
+\xac\x6f\xdd\x57\xb7\xb1\x6d\x83\xbb\xd2\x53\xe3\x10\x2e\x9f\xcd\
+\xb0\x00\xfc\x9b\x54\xf4\x99\x84\x8c\x2d\x86\xc3\xe1\x2b\x81\xd9\
+\xbf\x0e\xff\x9c\xfe\x28\x9e\x22\x08\x84\x80\xb0\x94\x32\x5c\xb2\
+\x03\x21\x84\x13\xf0\x00\xee\xdc\xd2\x5c\x56\x3c\x5b\xeb\x69\x79\
+\xb8\x51\x74\x18\x12\xe5\xc2\x75\x39\x3c\x74\x83\xc9\x78\x86\x10\
+\x10\x03\x96\x80\x48\x6e\x2d\x4a\xb9\x7a\x01\x28\x79\xab\xc8\x89\
+\x59\x00\x2b\x60\xcb\x2d\x0b\xa0\x02\x3a\x90\x02\xd2\x40\x12\xc8\
+\x48\x79\xab\x87\x26\xfe\x03\x26\x93\xd5\x41\x51\x76\x98\xdb\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
\x00\x00\x0b\xd7\
\x89\
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
@@ -1491,94 +1572,180 @@ 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\
+\x00\x00\x05\x24\
\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\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x06\xec\x00\x00\x06\xec\
+\x01\x1e\x75\x38\x35\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\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x00\x13\x74\x45\
+\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x52\x6f\x64\x6e\x65\x79\x20\
+\x44\x61\x77\x65\x73\x0e\xd8\x7e\x1d\x00\x00\x04\x82\x49\x44\x41\
+\x54\x48\x89\x8d\x96\x6d\x88\x54\x55\x18\xc7\x7f\xe7\xbe\xcd\xdc\
+\xb9\xb3\xb3\x33\xfb\x1a\xeb\xbe\xe9\x6e\xad\x2f\x69\xea\x4a\x68\
+\x8a\x22\x24\x94\x58\x7e\xaa\x48\x23\x30\x41\xa1\xec\x5b\x51\x41\
+\xf8\x31\xa3\x3e\x44\x94\x44\x92\x09\xa5\x46\x54\x84\x21\x59\x44\
+\x18\x96\x19\xe8\xb2\x5a\x9b\xba\xea\xa4\xfb\x66\xee\xce\xce\xec\
+\xec\xcc\xdc\xbd\xf3\x76\xef\xe9\x43\xb3\x63\xe6\xbe\x3d\xf0\x7c\
+\xfb\x9f\xff\xef\x3c\xcf\x3d\xf7\x39\x47\x48\x29\x99\x2d\x36\xbf\
+\x27\x7c\x6a\x40\x7f\xc0\x6f\xaa\x8f\x02\x64\x1d\xf7\x84\x3b\x51\
+\xb8\xf0\xed\x8b\x32\x37\xdb\x5a\x31\x1d\x60\xf7\x01\xa1\x67\x2a\
+\x42\x6f\x37\xd7\x76\xac\xaf\x0a\xd6\xd5\x07\x2c\x7f\xb5\x69\x05\
+\x7c\x52\xba\x38\xb6\x93\xb3\xed\x6c\x62\xdc\x8e\xdd\xea\x8b\x5d\
+\x39\x15\x4c\xa7\x5e\xfe\x70\x97\x2c\xcc\x19\xb0\xf5\xa0\xb1\xa2\
+\xb9\x7e\xfe\xa1\x65\x1d\xab\x96\x7a\x5a\x56\xc9\xbb\x0e\x92\x3b\
+\x75\x02\x81\xa1\x9a\x50\x50\xbd\x9e\x2b\xe7\xff\xe8\x1f\xbe\xb1\
+\xe3\xd8\xce\x7c\xf7\xac\x80\x6d\x9f\x87\xf7\x2d\x69\x59\xf9\x5c\
+\x5d\x7d\x4d\x9d\xe3\xa6\x67\xeb\x00\x00\x01\x35\x44\x7c\x78\x74\
+\xe4\x42\x5f\xd7\xc7\x47\x9f\x4a\xbe\x36\x2d\xe0\x89\xc3\xe6\xb3\
+\x6b\x96\xae\xdf\x6f\x04\xf5\xa0\x27\xdd\x39\x99\x87\xb4\x7a\xaa\
+\xb4\x05\xa4\x8a\x43\x8c\x24\xaf\x65\xce\xf4\xfc\xfa\xc2\x17\xcf\
+\x38\x9f\xdc\x05\x78\xfc\x80\xa8\x59\xdc\xb6\xfc\xb7\xc6\xd6\x79\
+\x6d\x73\x33\x17\x6c\xa8\xdc\x43\x83\xb1\x0c\x9f\x08\x72\xc9\xf9\
+\x9e\x73\x99\x4f\x19\xb8\xd1\x17\xbd\x18\xed\x59\xfd\xcd\x2e\x39\
+\x0a\xa0\x4c\xca\xab\xab\xea\x8e\x34\x35\x37\xcc\xd1\x1c\x56\x5a\
+\x4f\xd2\xea\x5b\x8d\x5f\x54\x10\x2b\x46\x39\x97\x39\x8c\x2b\x5d\
+\x5a\x9a\x5b\xdb\xaa\xab\x6a\x8f\x4c\xea\x14\xf8\xf7\xa3\xde\xd7\
+\xd2\xb1\xd6\x15\x73\x33\xaf\xd1\xdb\xb9\xd7\xdc\x88\x82\x4a\xda\
+\x1d\xe1\x97\xd4\x07\xb8\xb2\x08\x40\x51\xb8\xb4\x37\xcf\x5f\xbb\
+\xf5\xa0\xb1\xa2\x0c\xf0\xfb\xd4\x2d\x56\xd0\xb4\x40\x20\xc4\x54\
+\xa9\x94\x53\x53\xfc\xac\x0b\xed\x22\xa0\x86\xc9\x4b\x87\x4b\xce\
+\x09\xd2\xee\x2d\x14\xa1\xa2\x08\x0d\x21\x54\x02\x96\xdf\xf2\xfb\
+\xd4\x2d\x00\x1a\x80\x65\x55\x76\xaa\x86\x86\xbc\xdd\xb1\x52\x97\
+\xc5\x5d\xbb\x5f\x57\xb1\x9b\x88\xd6\x02\xc0\xad\x42\x0f\x97\xb3\
+\x3f\xa2\x08\xed\x0e\x8d\x66\xe8\x58\x66\xa8\xb3\x0c\x08\x9b\x91\
+\x26\x4f\x4a\x54\x45\x9d\xd1\x7c\x81\xff\x21\x1a\x7d\xcb\xf1\x3c\
+\x8f\x91\x42\x2f\xc7\xe3\x7b\x31\xd5\x10\x86\x12\xb8\x43\x57\x14\
+\x0a\xe1\x40\xa8\xa9\x0c\x30\x0d\xb3\x5a\x11\x3a\x0a\x0a\x4c\x61\
+\x0c\x10\x50\x23\x2c\x31\x1f\x43\xb8\x3a\x49\xf7\x26\xc7\xe3\x7b\
+\x89\xe5\xa3\x00\x58\x6a\x15\x11\xa3\x11\x53\x0d\x23\x10\xe4\xbd\
+\x1c\xa6\xe1\xab\x2e\x03\x9c\xfc\x44\x5c\x57\x8c\x96\xff\x9a\x77\
+\x98\x9b\x08\x28\x61\xba\xed\x2f\xf1\x64\x91\x4e\x73\x1b\x16\xb5\
+\x14\x65\x8e\x0b\xf6\x57\x0c\xe5\xcf\x97\xb5\xb6\x9b\xc0\x76\x12\
+\xf8\x14\x8b\xb0\xde\x48\xde\x4b\xe1\xe4\xb3\xf1\x32\x20\x69\x8f\
+\x0d\x08\xe9\xad\x14\xc2\x00\x60\x51\xe0\x11\x96\x04\x36\x23\x50\
+\x18\x2b\x0c\x52\x70\x73\xdc\xa3\x2d\x06\xa0\x3f\x77\x96\x53\xa9\
+\xfd\x53\x56\x99\xf3\x6c\x86\x73\xbd\x54\xa9\xb5\x24\x27\xc6\x07\
+\xca\x00\xdb\x49\x75\x25\x9d\xa1\xad\xe8\x3a\xa6\x5a\x89\x81\x85\
+\xf0\x34\x3c\xcf\xa3\xcd\xd8\x80\x2e\x4c\x14\xa1\x13\x2b\x5c\xe5\
+\x58\xe2\x55\x60\xe6\x09\xac\xba\x3a\xb6\x93\xe9\x2a\x03\xb2\x39\
+\xf7\x78\x2e\x53\x7c\x65\x3c\xd0\x67\x49\x3c\xbc\xa2\xa4\xb5\x76\
+\x0d\x9a\xf0\x53\x55\x3a\x31\xb6\x1b\xe7\x64\xea\x1d\x26\xbc\xc4\
+\x8c\xe6\x02\x85\x82\x2d\xed\x6c\xce\x3d\x0e\xa5\xff\xe0\xd8\xce\
+\x7c\x77\x74\xa0\xff\x74\x8d\xd2\x00\xc0\x60\xbe\x9b\x44\xb1\xbf\
+\xbc\xc8\x93\x2e\x97\x9c\xef\xb8\xea\xfc\x34\xa3\x39\x40\x8d\x52\
+\x47\x74\x70\xf0\xf4\xe4\x64\x2d\x1f\xfc\x78\x22\xb6\x3d\x31\xe4\
+\x44\x4d\xc5\xc2\xc3\xe5\x77\xfb\x6b\x3c\x59\xa4\x20\x1d\x06\xf2\
+\xe7\xf8\x21\xf9\xe6\xac\xe6\xa6\x12\x20\x31\xe4\x44\xe3\x89\xf8\
+\xf6\x72\x45\xff\x9f\xa6\x9d\x0b\xef\x7f\x3f\xe9\xff\xbb\xc2\x95\
+\x1e\x0b\xcd\x87\xc9\xca\x34\xd7\xb3\x67\x98\xad\xef\x9a\xd0\x08\
+\x3b\xb5\xe9\xae\xde\xde\x3d\x53\x4e\xd3\xc9\xd8\xf6\x59\x78\x5f\
+\xc7\xbc\x96\x1d\x6a\x4d\xa1\x3e\xe9\xc6\x67\xdd\x35\x40\x58\x8d\
+\xe0\x8e\x6a\xc3\xbd\x43\xfd\x87\x8e\x3e\x9d\x9a\xfe\x3e\x98\x8c\
+\x4d\x6f\x19\xab\xdb\xda\xeb\x3f\x5a\xd0\x5e\xb7\x28\xad\x24\x94\
+\xac\x9c\xfa\x46\xf3\x0b\x3f\x15\x6e\xd8\xfb\xeb\xda\xc8\xe5\x8b\
+\x5d\xb1\xe7\x7f\xde\x57\x3c\x2d\x65\x69\xea\x4d\x05\x10\x42\xa8\
+\x40\x35\x10\x36\x2b\xa8\xdd\xf8\x7a\xf0\xa5\x96\xe6\xea\x65\xa1\
+\x50\x20\x12\x08\x2b\x41\xa3\x42\xd1\x01\x72\x29\xaf\x38\x91\x2c\
+\x66\xd2\xa9\x6c\xf2\xfa\xf5\xd8\x9f\x27\xdf\x98\x78\x37\x67\x33\
+\x0a\x8c\x01\x49\x29\x65\x72\xda\x0a\x84\x10\x16\x10\x01\x2a\x4b\
+\x59\x61\x04\x89\x34\x3c\xa8\x2c\x6c\x5a\xa5\xad\x90\x12\x65\xe0\
+\x6c\xf1\xfc\xcd\xb3\x5e\xb4\x60\x33\x06\x64\x80\x14\x30\x5e\xca\
+\x84\x94\xb7\x1f\x00\xd3\xbe\x2a\x4a\x30\x1d\x30\x00\x5f\x29\x75\
+\x40\x05\x8a\x40\x0e\xc8\x03\x59\xa0\x20\xe5\xd4\x37\xd5\x3f\x13\
+\x05\x02\x8c\xec\xcf\x7e\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
+\x00\x00\x05\x64\
+\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\x0d\xd7\x00\x00\x0d\xd7\
+\x01\x42\x28\x9b\x78\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\xe1\x49\x44\
+\x41\x54\x48\x89\xb5\x95\x4b\x6c\x54\x55\x18\xc7\x7f\xe7\x9c\x3b\
+\xd3\x99\x32\xb5\xf4\x41\x1f\x38\x02\xa5\x8d\x4c\x54\xb0\x25\x48\
+\xa2\x12\x62\x14\x17\x46\x12\x64\x81\x26\xb0\x31\x18\x12\x36\x04\
+\xd2\x45\xe9\xca\x84\x5d\x29\x1a\x43\x80\x6e\x0c\x2b\xdc\x88\x09\
+\xa0\x21\x04\x35\x18\xa2\x26\x3e\xa3\x62\xa8\x4f\x7c\x61\x95\x81\
+\xb6\x4c\xcb\xbc\x67\xee\x39\x9f\x8b\xdb\xde\x4e\x2b\x10\x37\xde\
+\xe4\x4b\xee\xf3\xf7\xff\xfe\xdf\xf9\xee\x77\x94\x88\xf0\x7f\x1e\
+\xde\xdd\x1e\x9e\x53\xaa\x23\x6a\xcc\x33\xf1\x58\xec\xc9\xc6\xae\
+\x15\x2b\xeb\x97\x76\x2e\xb5\xc5\x62\x29\xfb\xd7\x5f\x63\xb9\x6b\
+\xd7\x7f\x2c\x55\x2a\xe7\x2b\xd6\x7e\xb0\x59\xa4\x70\x27\x86\xba\
+\xad\x03\xa5\xd4\x87\xb1\xd8\xcb\xc9\xc7\x1e\xdd\xdd\xd9\xde\xd6\
+\x11\xab\x8b\xa3\x4a\x45\xc8\xe5\xc1\x18\x48\x2c\xc2\x79\x86\x5c\
+\x21\xef\x7e\x1f\xbd\x7c\x65\xe2\xa7\x5f\xf7\x3c\x59\xad\xbe\xf7\
+\x9f\x04\xce\x2b\xb5\xa2\x75\x69\xe7\x1b\xa9\x0d\x8f\xaf\x4f\x54\
+\x6d\x84\x52\x29\x7c\x26\xc0\xec\xfb\x32\x13\x34\x24\xb8\x91\x9f\
+\x9e\xfa\xe5\xe3\x4f\xdf\xb6\xb7\xb2\xbb\x9f\x10\x29\xd5\xf2\xe6\
+\x09\xbc\xa7\x54\x6a\xc5\xda\xde\xf7\x7b\xba\xbb\x93\x3a\x57\x98\
+\x83\x2c\x80\x2e\x14\x11\xcf\xc3\xc6\x23\xf2\xdd\xa7\x9f\x7d\x91\
+\x1f\xbb\xf6\xf8\x13\x22\xfe\x2c\x53\xcf\x9e\x1c\x50\x4a\xb7\x2e\
+\x5f\x76\xa2\x67\xf9\xb2\x24\xd9\x3c\x4e\x24\x0c\x0b\xd8\x9a\x6b\
+\x07\xc1\x3d\xc0\x01\xce\xf7\x21\x5b\x54\x3d\x0f\xaf\x5e\xa7\xeb\
+\xeb\x5f\xab\x75\x10\x0a\x6c\x8a\xc7\x87\xee\x7f\xe8\x81\x3e\x29\
+\x56\x02\x80\x08\x95\xed\xdb\xa9\x6e\xd9\x32\x27\x52\x03\xb5\x9e\
+\x87\x1d\x18\xc0\xef\xed\x0d\xc5\x8d\x55\x3a\xd9\xf7\xe0\x0b\xef\
+\x46\xa3\xeb\x67\xb9\x1e\xc0\x05\xa5\xee\x5f\xf9\xd8\xfa\x17\xeb\
+\x7c\x67\x1c\x0a\x01\xec\x8e\x1d\xf8\xcf\x3d\x17\x64\xa1\x14\xfa\
+\xf4\xe9\xb0\x3c\x2e\x12\x81\xfd\xfb\xa1\xb7\x17\xfa\xfa\x70\x43\
+\x43\xf0\xf5\xd7\x88\x15\x9a\x1b\x9b\x96\x34\x25\xdb\x5f\x47\xa9\
+\x5e\x44\x44\x03\x78\x9e\xf7\x52\xc7\xe2\xa6\x25\x0e\x15\x64\xe9\
+\x79\xd8\x9e\x9e\xd0\xa6\xdb\xbe\x1d\x7f\xeb\xd6\x20\xd3\x68\x14\
+\x06\x07\x51\x7d\x7d\x28\xa5\x50\x91\x08\x74\x77\x63\x95\xc2\x29\
+\x85\xad\x38\x5a\xdb\x5a\x97\x9d\x85\x9e\xd0\xc1\xa2\xd6\x96\x07\
+\x11\x70\x5a\x07\x59\x3a\x07\x43\x43\xa8\xc1\x41\x58\xbd\x3a\x50\
+\xd9\xb1\x03\x17\x89\xa0\x52\x29\xd4\x9a\x35\xa1\xb8\x3d\x79\x12\
+\xff\xcc\x19\xc4\x18\x10\x41\xb4\xa6\x2e\x16\x5b\x1c\xd1\xfa\x29\
+\xe0\x67\x0d\x10\x6b\x6d\xba\xcf\x39\xc1\x69\x3d\x17\xd6\xe2\x86\
+\x87\x91\xcb\x97\xe7\x16\xec\xf9\xe7\x43\xb8\x88\xe0\xbf\xf9\x26\
+\xd5\x53\xa7\x82\xc4\xb4\xc6\x79\x1e\x62\x0c\x26\x12\xa5\x2e\x11\
+\xdf\x00\xa0\xdf\x52\x2a\x51\xb7\x28\xde\xee\xbc\xc8\x7c\x01\xad\
+\x71\xce\xe1\xbf\xf2\x0a\x32\x3a\x3a\x57\x2e\xe7\xf0\x7d\x9f\xe2\
+\xa1\x43\x64\x07\x06\x28\x5f\xb9\x82\x2d\x95\x70\xc6\x04\xdf\x18\
+\x83\xad\x5a\xe2\x4b\x9a\x97\x87\x25\xc2\x98\x60\xe1\x6a\x7b\x7b\
+\x06\x28\xc6\x20\x4a\x81\x13\x9c\xb8\xf0\x1f\x70\x80\xb5\x16\x3f\
+\x9d\x46\xd2\x69\x74\x4b\x0b\x5e\x32\x89\x6e\x6e\xc6\x96\xcb\x88\
+\xb8\xa0\x8b\xb6\x89\xe4\xbe\x79\x68\xd5\x75\xb9\xd7\xb4\x8b\x95\
+\x00\xae\x54\x00\x8f\x44\x88\xec\xdb\x07\xa9\x14\xd6\xd9\xda\xf6\
+\x26\xd6\xdf\x8f\x03\x0a\x87\x0f\x07\x5d\x37\x31\x41\x65\x7c\x1c\
+\xe2\x71\xbc\xce\x26\xf2\x13\x53\x7f\x84\xff\x41\x71\x32\xf3\xa7\
+\x3f\x39\x81\xd3\x26\xb4\xea\x97\xca\xe8\x5d\xbb\x90\x55\xab\x70\
+\x2e\xc8\xa6\xf0\xea\xab\x54\x3f\xfa\x28\x14\xa9\xef\xef\x27\xb6\
+\x77\x2f\x56\x24\x08\xc0\x2f\x14\xa8\x7a\x9a\x72\xae\xf8\x71\x58\
+\xa2\xc2\x44\x66\xb4\x9c\xcd\x3c\xeb\x46\x7f\x40\x25\x12\x38\xdf\
+\x27\x3e\x32\x82\x5e\xbb\x36\x84\xe5\x87\x87\x29\x1c\x3b\x86\x44\
+\xa3\x34\x1e\x3f\x4e\xdd\xc6\x8d\x00\x24\xfa\xfb\x71\xbe\x4f\xf6\
+\xc8\x91\xb0\xb4\x3e\x4c\x39\xe7\x2e\x84\x0e\xf0\xfd\xe3\x19\xe7\
+\xc6\x2d\x8e\xca\xf8\x38\xd5\x4c\x06\xff\xd2\xa5\x10\x9e\x3b\x78\
+\x90\xdc\xd1\xa3\x41\x96\xe5\x32\x93\x3b\x77\x52\xba\x78\x31\x28\
+\x63\xb9\x4c\xe9\xdb\x6f\xf1\x67\x5c\xe8\xf6\x26\xa6\x27\x32\x57\
+\x37\xc3\x15\xa8\x19\x76\x17\xea\xeb\x87\xbb\xfa\x52\xfd\xf6\xf2\
+\x2f\x46\x24\x58\x8b\xc4\xe0\x20\xf6\xd6\x2d\x72\x23\x23\x41\x8f\
+\xd7\x36\x41\x34\x4a\xeb\xc8\x08\xd9\x13\x27\x28\x5c\xbc\x18\x0c\
+\xc4\xa8\x87\xac\xec\x18\xff\xfb\xab\x9f\x36\x6f\x16\xf9\x7c\x9e\
+\xc0\x01\xa5\xf4\xa6\x64\xfb\x67\x1d\x0d\x0d\xeb\x2a\x63\xd7\x91\
+\x05\xc0\xd9\xde\x9f\x27\x52\x73\x0d\x50\x97\xba\xcf\xa5\xbf\xff\
+\x7d\xe4\xe9\x42\x69\xcf\xac\xfb\x79\xe3\xfa\xac\x52\xa9\xb6\x54\
+\xd7\xfb\x8d\xc6\x24\x2b\x7f\xa4\xff\x05\x58\x08\x0d\x47\xb7\x67\
+\x88\x76\x77\xca\x74\xfa\xe6\x17\x92\x9e\x9c\x37\xae\x6f\xbb\xe1\
+\xc4\xda\x9a\xdf\x58\xb2\x62\xe9\x23\xfe\x6f\xd7\xa2\x36\x5f\x9c\
+\x2f\xb0\x40\xc4\x6b\x6b\x44\x9a\x13\x99\xc9\x1f\xc7\xde\x89\x15\
+\x4a\x77\xdf\x70\x00\x94\x52\x0d\x31\x68\x39\x12\x31\x03\xa9\x55\
+\xcb\xb7\x35\x18\xd3\xaa\x7c\x8b\x9d\xce\x53\x9d\xce\x83\xd1\x98\
+\xc5\x09\x74\x43\x0c\xab\x95\x4c\x66\x72\x63\x17\xae\x5e\x3f\x78\
+\x08\xde\x05\xa6\x80\x29\xb9\x93\x03\xa5\x54\x04\x68\x01\x16\x03\
+\x4d\xbd\xd0\xb5\x49\xeb\x8d\x5d\x9e\xe9\x6b\x69\x69\x68\x6b\x6c\
+\x5c\xd4\x58\xf5\xad\xbd\x99\xc9\x4d\xdf\x98\xce\x5f\x1b\xf5\xed\
+\x97\xe7\xe0\x93\x71\x48\xcf\xc2\x81\x8c\x88\x64\xef\xe8\x60\x46\
+\xa8\x1e\xb8\x67\x26\xea\x81\x38\x50\x07\x18\x40\x11\xec\x3b\x15\
+\xa0\x0c\xe4\x81\x1c\x70\x0b\xc8\xca\xec\x8c\x98\x39\xfe\x01\x76\
+\x95\xba\xf1\x06\x3a\xff\x81\x00\x00\x00\x00\x49\x45\x4e\x44\xae\
+\x42\x60\x82\
"
qt_resource_name = "\
@@ -1590,6 +1757,11 @@ qt_resource_name = "\
\x05\xcd\xf4\xe7\
\x00\x63\
\x00\x6f\x00\x6e\x00\x6e\x00\x5f\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x13\
+\x09\xd2\x6c\x67\
+\x00\x45\
+\x00\x6d\x00\x62\x00\x6c\x00\x65\x00\x6d\x00\x2d\x00\x71\x00\x75\x00\x65\x00\x73\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x70\
+\x00\x6e\x00\x67\
\x00\x12\
\x04\xe4\x91\x47\
\x00\x63\
@@ -1609,21 +1781,28 @@ 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\
+\x00\x11\
+\x06\x1a\x44\xa7\
+\x00\x44\
+\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x61\x00\x63\x00\x63\x00\x65\x00\x70\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\
+\x00\x10\
+\x0f\xc3\x90\x67\
+\x00\x44\
+\x00\x69\x00\x61\x00\x6c\x00\x6f\x00\x67\x00\x2d\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\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\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\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x02\
+\x00\x00\x00\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x32\x3e\
+\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x12\xe7\
\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\
-\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x20\xbd\
+\x00\x00\x01\x02\x00\x00\x00\x00\x00\x01\x00\x00\x60\xc7\
+\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x1e\xc2\
+\x00\x00\x00\x34\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xf7\
+\x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\x25\xad\
+\x00\x00\x01\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x65\xef\
"
def qInitResources():
diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py
index 6e8abc1f..6f13a1ac 100644
--- a/src/leap/gui/progress.py
+++ b/src/leap/gui/progress.py
@@ -17,11 +17,31 @@ from leap.gui.threads import FunThread
from leap.gui import mainwindow_rc
-CHECKMARK_IMG = ":/images/checked.png"
+ICON_CHECKMARK = ":/images/Dialog-accept.png"
+ICON_FAILED = ":/images/Dialog-error.png"
+ICON_WAITING = ":/images/Emblem-question.png"
logger = logging.getLogger(__name__)
+# XXX import this from threads
+def delay(obj, method_str=None, call_args=None):
+ """
+ this is a hack to get responsiveness in the ui
+ """
+ if callable(obj) and not method_str:
+ QtCore.QTimer().singleShot(
+ 50,
+ lambda: obj())
+ return
+
+ if method_str:
+ QtCore.QTimer().singleShot(
+ 50,
+ lambda: QtCore.QMetaObject.invokeMethod(
+ obj, method_str))
+
+
class ImgWidget(QtGui.QWidget):
# XXX move to widgets
@@ -125,6 +145,8 @@ class StepsTableWidget(QtGui.QTableWidget):
# this disables the table grid.
# we should add alignment to the ImgWidget (it's top-left now)
self.setShowGrid(False)
+ self.setFocusPolicy(QtCore.Qt.NoFocus)
+ #self.setStyleSheet("QTableView{outline: 0;}")
# XXX change image for done to rc
@@ -144,46 +166,94 @@ class StepsTableWidget(QtGui.QTableWidget):
# 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
- """
+class WithStepsMixIn(object):
- # signals
+ # worker threads for checks
- stepChanged = QtCore.pyqtSignal([str, int])
+ def setupStepsProcessingQueue(self):
+ self.steps_queue = Queue.Queue()
+ self.stepscheck_timer = QtCore.QTimer()
+ self.stepscheck_timer.timeout.connect(self.processStepsQueue)
+ self.stepscheck_timer.start(100)
+ # we need to keep a reference to child threads
+ self.threads = []
- def __init__(self, parent=None):
- super(ValidationPage, self).__init__(parent)
+ def do_checks(self):
- self.steps = ProgressStepContainer()
- self.progress = QtGui.QProgressBar(self)
+ # yo dawg, I heard you like checks
+ # so I put a __do_checks in your do_checks
+ # for calling others' _do_checks
- # steps table widget
- self.stepsTableWidget = StepsTableWidget(self)
+ def __do_checks(fun=None, queue=None):
- layout = QtGui.QVBoxLayout()
- layout.addWidget(self.progress)
- layout.addWidget(self.stepsTableWidget)
+ for checkcase in fun():
+ checkmsg, checkfun = checkcase
- self.setLayout(layout)
- self.layout = layout
+ queue.put(checkmsg)
+ if checkfun() is False:
+ queue.put("failed")
+ break
- self.timer = QtCore.QTimer()
+ t = FunThread(fun=partial(
+ __do_checks,
+ fun=self._do_checks,
+ queue=self.steps_queue))
+ t.finished.connect(self.on_checks_validation_ready)
+ t.begin()
+ self.threads.append(t)
- # connect the new step status
- # signal to status handler
- self.stepChanged.connect(
- self.onStepStatusChanged)
+ def fail(self, err=None):
+ """
+ return failed state
+ and send error notification as
+ a nice side effect
+ """
+ wizard = self.wizard()
+ senderr = lambda err: wizard.set_validation_error(
+ self.current_page, err)
+ self.set_undone()
+ if err:
+ senderr(err)
+ return False
+
+ @QtCore.pyqtSlot()
+ def launch_checks(self):
+ self.do_checks()
+
+ # slot
+ #@QtCore.pyqtSlot(str, int)
+ def onStepStatusChanged(self, status, progress=None):
+ if status not in ("head_sentinel", "end_sentinel"):
+ self.add_status_line(status)
+ if status in ("end_sentinel"):
+ self.checks_finished = True
+ self.set_checked_icon()
+ if progress and hasattr(self, 'progress'):
+ self.progress.setValue(progress)
+ self.progress.update()
+ def processStepsQueue(self):
+ """
+ consume steps queue
+ and pass messages
+ to the ui updater functions
+ """
+ while self.steps_queue.qsize():
+ try:
+ status = self.steps_queue.get(0)
+ if status == "failed":
+ self.set_failed_icon()
+ else:
+ self.onStepStatusChanged(*status)
+ except Queue.Empty:
+ pass
+
+ def setupSteps(self):
+ self.steps = ProgressStepContainer()
+ # steps table widget
+ self.stepsTableWidget = StepsTableWidget(self)
+ zeros = (0, 0, 0, 0)
+ self.stepsTableWidget.setContentsMargins(*zeros)
self.errors = OrderedDict()
def set_error(self, name, error):
@@ -230,34 +300,63 @@ class ValidationPage(QtGui.QWizardPage):
def resizeTable(self):
# resize first column to ~80%
table = self.stepsTableWidget
- FIRST_COLUMN_PERCENT = 0.75
+ FIRST_COLUMN_PERCENT = 0.70
width = table.width()
logger.debug('populate table. width=%s' % width)
table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT)
- def onStepStatusChanged(self, status, progress=None):
- if status not in ("head_sentinel", "end_sentinel"):
- self.add_status_line(status)
- if progress:
- self.progress.setValue(progress)
- self.progress.update()
+ def set_item_icon(self, img=ICON_CHECKMARK, current=True):
+ """
+ mark the last item
+ as done
+ """
+ # setting cell widget.
+ # see note on StepsTableWidget about plans to
+ # change this for a better solution.
+ index = len(self.steps)
+ table = self.stepsTableWidget
+ _index = index - 1 if current else index - 2
+ table.setCellWidget(
+ _index,
+ ProgressStep.DONE,
+ ImgWidget(img=img))
+ table.update()
+
+ def set_failed_icon(self):
+ self.set_item_icon(img=ICON_FAILED, current=True)
+
+ def set_checking_icon(self):
+ self.set_item_icon(img=ICON_WAITING, current=True)
+
+ def set_checked_icon(self, current=True):
+ self.set_item_icon(current=current)
def add_status_line(self, message):
+ """
+ adds a new status line
+ and mark the next-to-last item
+ as done
+ """
index = len(self.steps)
step = ProgressStep(message, False, index=index)
self.steps.addStep(step)
self.populateStepsTable()
- table = self.stepsTableWidget
+ self.set_checking_icon()
+ self.set_checked_icon(current=False)
- # setting cell widget.
- # see note on StepsTableWidget about plans to
- # change this for a better solution.
+ # Sets/unsets done flag
+ # for isComplete checks
- table.setCellWidget(
- index - 1,
- ProgressStep.DONE,
- ImgWidget(img=CHECKMARK_IMG))
- table.update()
+ def set_done(self):
+ self.done = True
+ self.completeChanged.emit()
+
+ def set_undone(self):
+ self.done = False
+ self.completeChanged.emit()
+
+ def is_done(self):
+ return self.done
def go_back(self):
self.wizard().back()
@@ -265,6 +364,99 @@ class ValidationPage(QtGui.QWizardPage):
def go_next(self):
self.wizard().next()
+
+"""
+We will use one base class for the intermediate pages
+and another one for the in-page validations, both sharing the creation
+of the tablewidgets.
+The logic of this split comes from where I was trying to solve
+the ui update using signals, but now that it's working well with
+queues I could join them again.
+"""
+
+import Queue
+from functools import partial
+
+
+class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn):
+
+ def __init__(self, parent=None):
+ super(InlineValidationPage, self).__init__(parent)
+ self.setupStepsProcessingQueue()
+ self.done = False
+
+ # slot
+
+ @QtCore.pyqtSlot()
+ def showStepsFrame(self):
+ self.valFrame.show()
+ self.update()
+
+ # progress frame
+
+ def setupValidationFrame(self):
+ qframe = QtGui.QFrame
+ valFrame = qframe()
+ valFrame.setFrameStyle(qframe.NoFrame)
+ valframeLayout = QtGui.QVBoxLayout()
+ zeros = (0, 0, 0, 0)
+ valframeLayout.setContentsMargins(*zeros)
+
+ valframeLayout.addWidget(self.stepsTableWidget)
+ valFrame.setLayout(valframeLayout)
+ self.valFrame = valFrame
+
+
+class ValidationPage(QtGui.QWizardPage, WithStepsMixIn):
+ """
+ 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, int])
+
+ def __init__(self, parent=None):
+ super(ValidationPage, self).__init__(parent)
+ self.setupSteps()
+ #self.connect_step_status()
+
+ layout = QtGui.QVBoxLayout()
+ self.progress = QtGui.QProgressBar(self)
+ layout.addWidget(self.progress)
+ layout.addWidget(self.stepsTableWidget)
+
+ self.setLayout(layout)
+ self.layout = layout
+
+ self.timer = QtCore.QTimer()
+ self.done = False
+
+ self.setupStepsProcessingQueue()
+
+ def isComplete(self):
+ return self.is_done()
+
+ ########################
+
+ def show_progress(self):
+ self.progress.show()
+ self.stepsTableWidget.show()
+
+ def hide_progress(self):
+ self.progress.hide()
+ self.stepsTableWidget.hide()
+
+ # pagewizard methods.
+ # if overriden, child classes should call super.
+
def initializePage(self):
self.clean_errors()
self.clean_wizard_errors()
@@ -272,16 +464,3 @@ class ValidationPage(QtGui.QWizardPage):
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(update_signal=signal))
- self.checks.finished.connect(self._do_validation)
- self.checks.begin()
- #logger.debug('check thread started!')
- #logger.debug('waiting for it to terminate...')
- self.checks.wait()
diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py
index 759817ce..b482922e 100644
--- a/src/leap/gui/styles.py
+++ b/src/leap/gui/styles.py
@@ -1,4 +1,16 @@
-ErrorLabelStyleSheet = """
-QLabel { color: red;
- font-weight: bold}
+GreenLineEdit = "QLabel {color: green; font-weight: bold}"
+ErrorLabelStyleSheet = """QLabel { color: red; font-weight: bold }"""
+ErrorLineEdit = """QLineEdit { border: 1px solid red; }"""
+
+
+# XXX this is bad.
+# and you should feel bad for it.
+# The original style has a sort of box color
+# white/beige left-top/right-bottom or something like
+# that.
+
+RegularLineEdit = """
+QLineEdit {
+ border: 1px solid black;
+}
"""
diff --git a/src/leap/gui/test_mainwindow_rc.py b/src/leap/gui/test_mainwindow_rc.py
index c2fb3f78..c5abb4aa 100644
--- a/src/leap/gui/test_mainwindow_rc.py
+++ b/src/leap/gui/test_mainwindow_rc.py
@@ -26,4 +26,4 @@ class MainWindowResourcesTest(unittest.TestCase):
def test_mainwindow_resources_hash(self):
self.assertEqual(
hashlib.md5(mainwindow_rc.qt_resource_data).hexdigest(),
- 'cc7f55e551df55e39c7dbedc1f7de4c2')
+ '53e196f29061d8f08f112e5a2e64eb53')
diff --git a/src/leap/gui/threads.py b/src/leap/gui/threads.py
index 176c19b1..8aad8866 100644
--- a/src/leap/gui/threads.py
+++ b/src/leap/gui/threads.py
@@ -3,10 +3,16 @@ from PyQt4 import QtCore
class FunThread(QtCore.QThread):
- def __init__(self, fun, parent=None):
+ def __init__(self, fun=None, parent=None):
+
QtCore.QThread.__init__(self, parent)
+ self.exiting = False
self.fun = fun
+ def __del__(self):
+ self.exiting = True
+ self.wait()
+
def run(self):
if self.fun:
self.fun()