From 4153bee65d2541b99d4e41aaaf2fd6b2b71b2cc3 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 19 Nov 2012 23:15:36 +0900 Subject: disable "next" button during validation and wait for user to click it. Closes #973 --- src/leap/gui/firstrun/providerinfo.py | 48 +++++++++++++++++++++++----- src/leap/gui/firstrun/providersetup.py | 19 ++++++----- src/leap/gui/firstrun/register.py | 1 + src/leap/gui/firstrun/regvalidation.py | 58 ++++++++++++++++++++-------------- src/leap/gui/firstrun/wizard.py | 3 ++ src/leap/gui/progress.py | 28 ++++++++++++++++ 6 files changed, 117 insertions(+), 40 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index e642fcd0..8a3243fc 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -32,6 +32,7 @@ class ProviderInfoPage(ValidationPage): QtGui.QPixmap(APP_LOGO)) self.prev_page = "providerselection" + self.infoWidget = None #self.current_page = "providerinfo" def create_info_panel(self): @@ -62,6 +63,8 @@ class ProviderInfoPage(ValidationPage): # 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 @@ -93,8 +96,11 @@ class ProviderInfoPage(ValidationPage): """ executes actual checks in a separate thread """ + finish = lambda: update_signal.emit("end_sentinel", 100) + def pause_and_finish(): - update_signal.emit("end_sentinel", 100) + # only for local debug + finish() pause_for_user() wizard = self.wizard() @@ -164,13 +170,13 @@ class ProviderInfoPage(ValidationPage): #self.did_cert_check = True #self.completeChanged.emit() #return False - pause_and_finish() + finish() return False except baseexceptions.LeapException as exc: wizard.set_validation_error( prevpage, exc.usermessage) - pause_and_finish() + finish() return False ################################## @@ -190,19 +196,20 @@ class ProviderInfoPage(ValidationPage): wizard.set_validation_error( prevpage, "Could not get info from provider.") - pause_and_finish() + finish() return False except requests.exceptions.ConnectionError: wizard.set_validation_error( prevpage, "Could not download provider info " "(refused conn.).") - pause_and_finish() + finish() return False # XXX catch more errors... # We're done! - pause_and_finish() + self.set_done() + finish() def _do_validation(self): """ @@ -214,8 +221,7 @@ class ProviderInfoPage(ValidationPage): errors = self.wizard().get_validation_error(prevpage) if not errors: - self.progress.hide() - self.stepsTableWidget.hide() + self.hide_progress() self.create_info_panel() self.show_provider_info() @@ -228,3 +234,29 @@ class ProviderInfoPage(ValidationPage): wizard = self.wizard() next_ = "providersetupvalidation" return wizard.get_page_index(next_) + + #def isComplete(self): + #return self.is_done() + + def initializePage(self): + logger.error('INITIALIZE PAGE --------------') + logger.error('**') + logger.error('**') + super(ProviderInfoPage, self).initializePage() + self.show_progress() + self.set_undone() + self.completeChanged.emit() + + def cleanupPage(self): + logger.error('CLEANUP PAGE --------------') + + del self.wizard().providerconfig + + if self.infoWidget: + QtCore.QObjectCleanupHandler().add( + self.infoWidget) + + # refactor this into some kind of destructor + del self.displayName + del self.description + del self.enrollment_policy diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 3fb9a19b..5b50db87 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -78,12 +78,12 @@ class ProviderSetupValidationPage(ValidationPage): "Authentication error: %s" % exc.message) return False - pause_for_user() + #pause_for_user() ####################################### update_signal.emit('Fetching CA certificate', 30) - pause_for_user() + #pause_for_user() if pconfig: ca_cert_uri = pconfig.get('ca_cert_uri').geturl() @@ -100,7 +100,7 @@ class ProviderSetupValidationPage(ValidationPage): pCertChecker.download_ca_cert( uri=ca_cert_uri, verify=False) - pause_for_user() + #pause_for_user() update_signal.emit('Checking CA fingerprint', 66) #ca_cert_fingerprint = pconfig.get('ca_cert_fingerprint', None) @@ -133,11 +133,13 @@ class ProviderSetupValidationPage(ValidationPage): # XXX update validationMsg # should catch exception #return False - pause_for_user() + + #pause_for_user() #ca_cert_path = checker.ca_cert_path + self.set_done() update_signal.emit('end_sentinel', 100) - pause_for_user() + #pause_for_user() def _do_validation(self): """ @@ -153,10 +155,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() diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index b46dd4cd..e1a8149c 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -59,6 +59,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! diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index dbe30d3c..6db2bf6e 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -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' @@ -85,6 +79,7 @@ class RegisterUserValidationPage(ValidationPage): ########################################### # only if from signup + # MOVE TO SIGNUP PAGE... if is_signup: signup = auth.LeapSRPRegister( schema="https", @@ -97,6 +92,8 @@ class RegisterUserValidationPage(ValidationPage): # 1) register user ################################################## # only if from signup. + # XXX MOVE THIS STEP TO SIGNUP-IN-PLACE VALIDATION + # WIDGET.......................................... if is_signup: @@ -113,7 +110,7 @@ class RegisterUserValidationPage(ValidationPage): self.set_error( step, "Error connecting to provider (timeout)") - pause_for_user() + #pause_for_user() return False except requests.exceptions.ConnectionError as exc: @@ -123,9 +120,9 @@ class RegisterUserValidationPage(ValidationPage): "Error connecting to provider " "(connection error)") # XXX we should signal a BAD step - pause_for_user() + #pause_for_user() update_signal.emit("connection error!", 50) - pause_for_user() + #pause_for_user() return False # XXX check for != OK instead??? @@ -147,10 +144,10 @@ class RegisterUserValidationPage(ValidationPage): self.set_error( step, 'Username not available.') - pause_for_user() + #pause_for_user() return False - pause_for_user() + #pause_for_user() ################################################## # 2) fetching eip service config @@ -168,9 +165,9 @@ class RegisterUserValidationPage(ValidationPage): self.set_error( step, 'Could not download eip config.') - pause_for_user() + #pause_for_user() return False - pause_for_user() + #pause_for_user() ################################################## # 3) getting client certificate @@ -192,16 +189,17 @@ class RegisterUserValidationPage(ValidationPage): "Authentication error: %s" % exc.message) return False - pause_for_user() + #pause_for_user() ################ # end ! ################ update_signal.emit("end_sentinel", 100) - pause_for_user() + #pause_for_user() # here we go! :) + # this should be called CONNECT PAGE AGAIN. self.run_eip_checks_for_provider_and_connect(_domain) def run_eip_checks_for_provider_and_connect(self, domain): @@ -225,6 +223,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 +247,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 +260,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..8efa2018 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -137,6 +137,9 @@ class FirstRunWizard(QtGui.QWizard): QtGui.QWizard.BackgroundPixmap, QtGui.QPixmap(':/images/background.png')) + # set options + self.setOption(QtGui.QWizard.IndependentPages, on=False) + self.setWindowTitle("First Run Wizard") # TODO: set style for MAC / windows ... diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 6e8abc1f..687356aa 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -185,6 +185,26 @@ class ValidationPage(QtGui.QWizardPage): self.onStepStatusChanged) self.errors = OrderedDict() + self.done = False + + # Sets/unsets done flag + # for isComplete checks + + 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 isComplete(self): + return self.is_done() + + ######################## def set_error(self, name, error): self.errors[name] = error @@ -285,3 +305,11 @@ class ValidationPage(QtGui.QWizardPage): #logger.debug('check thread started!') #logger.debug('waiting for it to terminate...') self.checks.wait() + + def show_progress(self): + self.progress.show() + self.stepsTableWidget.show() + + def hide_progress(self): + self.progress.hide() + self.stepsTableWidget.hide() -- cgit v1.2.3 From 7ab2ea1adb82d8c1c6bbae4dc58a157326f579a2 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 19 Nov 2012 23:23:50 +0900 Subject: set commit page (no back button after validation) --- src/leap/gui/firstrun/register.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index e1a8149c..f872a127 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -29,6 +29,13 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): 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.initUI() + + def initUI(self): userNameLabel = QtGui.QLabel("User &name:") userNameLineEdit = QtGui.QLineEdit() userNameLineEdit.cursorPositionChanged.connect( -- cgit v1.2.3 From a91001e9b84896e8df10ab13aad2dbccd3b6cf36 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 19 Nov 2012 23:34:03 +0900 Subject: add domain name to providerinfo page --- src/leap/gui/firstrun/providerinfo.py | 9 +++------ src/leap/gui/firstrun/providersetup.py | 5 +++++ 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 8a3243fc..c5f39938 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -79,8 +79,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( - "%s" % display_name) + "%s https://%s" % (display_name, domain_name)) desc = pconfig.get('description') description_text = desc[lang] if desc else '' @@ -239,17 +241,12 @@ class ProviderInfoPage(ValidationPage): #return self.is_done() def initializePage(self): - logger.error('INITIALIZE PAGE --------------') - logger.error('**') - logger.error('**') super(ProviderInfoPage, self).initializePage() self.show_progress() self.set_undone() self.completeChanged.emit() def cleanupPage(self): - logger.error('CLEANUP PAGE --------------') - del self.wizard().providerconfig if self.infoWidget: diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 5b50db87..0f0bdbd0 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -172,3 +172,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() -- cgit v1.2.3 From d225d5a4eb0a8e63eb11a0311c732bda88e8385b Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 20 Nov 2012 04:11:01 +0900 Subject: TableWidget refactor so we can use it also in the inline validation widgets. --- src/leap/gui/firstrun/providerselect.py | 107 ++++++++++++++++---- src/leap/gui/progress.py | 169 +++++++++++++++++++------------- 2 files changed, 187 insertions(+), 89 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 8d1aa869..fc030cf3 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -11,12 +11,13 @@ from PyQt4 import QtGui #from leap.eip import exceptions as eipexceptions from leap.gui.constants import APP_LOGO +from leap.gui.progress import InlineValidationPage from leap.gui.styles import ErrorLabelStyleSheet logger = logging.getLogger(__name__) -class SelectProviderPage(QtGui.QWizardPage): +class SelectProviderPage(InlineValidationPage): def __init__(self, parent=None, providers=None): super(SelectProviderPage, self).__init__(parent) @@ -32,6 +33,18 @@ class SelectProviderPage(QtGui.QWizardPage): self.did_cert_check = False self.current_page = 'providerselection' + self.is_done = False + + self.setupSteps() + self.setupUI() + + self.stepChanged.connect( + self.onStepStatusChanged) + + 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 @@ -61,6 +74,8 @@ class SelectProviderPage(QtGui.QWizardPage): validationMsg = QtGui.QLabel("") validationMsg.setStyleSheet(ErrorLabelStyleSheet) self.validationMsg = validationMsg + providerCheckButton = QtGui.QPushButton("check") + self.providerCheckButton = providerCheckButton # cert info @@ -81,25 +96,66 @@ 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, 1, 4, 2) + self.valFrame.hide() + + self.setLayout(layout) - # XXX get a groupbox or something.... + # certinfo + + def setupCertInfoGroup(self): + # XXX not used now. certinfoGroup = QtGui.QGroupBox("Certificate validation") certinfoLayout = QtGui.QVBoxLayout() certinfoLayout.addWidget(self.certInfo) certinfoLayout.addWidget(self.certWarning) certinfoLayout.addWidget(self.trustProviderCertCheckBox) certinfoGroup.setLayout(certinfoLayout) + self.certinfoGroup = self.certinfoGroup + + # progress frame + + def setupValidationFrame(self): + qframe = QtGui.QFrame + valFrame = qframe() + valFrame.setFrameStyle(qframe.StyledPanel) # | qframe.Sunken) + valframeLayout = QtGui.QVBoxLayout() + + #dummylabel = QtGui.QLabel('test foo') + #valframeLayout.addWidget(dummylabel) + valframeLayout.addWidget(self.stepsTableWidget) + valFrame.setLayout(valframeLayout) + self.valFrame = valFrame + + # check domain + + def onCheckButtonClicked(self): + import time + time.sleep(1) + self.is_done = True + self.providerCheckButton.setDisabled(True) + self.stepChanged.emit('foo check', 0) + self.valFrame.show() + self.completeChanged.emit() - layout.addWidget(certinfoGroup, 4, 1, 4, 2) - self.certinfoGroup = certinfoGroup - self.certinfoGroup.hide() - - self.setLayout(layout) + # cert trust verification def is_insecure_cert_trusted(self): return self.trustProviderCertCheckBox.isChecked() @@ -117,7 +173,20 @@ 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 trust this provider certificate?") + self.certInfo.setText( + 'SHA-256 fingerprint: %s
' % certinfo) + self.certInfo.setWordWrap(True) + self.certinfoGroup.show() + def onProviderChanged(self, text): + provider = self.providerNameEdit.text() + if provider: + self.providerCheckButton.setDisabled(False) + else: + self.providerCheckButton.setDisabled(True) self.completeChanged.emit() def reset_validation_status(self): @@ -126,29 +195,23 @@ class SelectProviderPage(QtGui.QWizardPage): """ 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 trust this provider certificate?") - self.certInfo.setText( - 'SHA-256 fingerprint: %s
' % certinfo) - self.certInfo.setWordWrap(True) - self.certinfoGroup.show() - # 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): @@ -195,7 +258,11 @@ 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) def validatePage(self): # some cleanup before we leave the page diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 687356aa..3ade28b3 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -144,67 +144,28 @@ 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 - - stepChanged = QtCore.pyqtSignal([str, int]) + def connect_step_status(self): + print 'connect method called' + self.stepChanged.connect( + self.onStepStatusChanged) - def __init__(self, parent=None): - super(ValidationPage, self).__init__(parent) + # slot + #@QtCore.pyqtSlot(QtCore.QString, int) + def onStepStatusChanged(self, status, progress=None): + import pdb4qt; pdb4qt.set_trace() + if status not in ("head_sentinel", "end_sentinel"): + self.add_status_line(status) + if progress and hasattr(self, 'progress'): + self.progress.setValue(progress) + self.progress.update() + def setupSteps(self): self.steps = ProgressStepContainer() - self.progress = QtGui.QProgressBar(self) - # 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() - self.done = False - - # Sets/unsets done flag - # for isComplete checks - - 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 isComplete(self): - return self.is_done() - - ######################## def set_error(self, name, error): self.errors[name] = error @@ -255,13 +216,6 @@ class ValidationPage(QtGui.QWizardPage): 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 add_status_line(self, message): index = len(self.steps) step = ProgressStep(message, False, index=index) @@ -279,20 +233,86 @@ class ValidationPage(QtGui.QWizardPage): ImgWidget(img=CHECKMARK_IMG)) table.update() + +""" +Resist the temptation to refactor the declaration of the signal +to the mixin. +PyQt and multiple inheritance do not mix well together. +You can only have one QObject base. +Therefore, 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. +""" + + +class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): + + # signals + stepChanged = QtCore.pyqtSignal([str, int]) + + def __init__(self, parent=None): + super(InlineValidationPage, self).__init__(parent) + self.connect_step_status() + + +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 + + # Sets/unsets done flag + # for isComplete checks + + 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 isComplete(self): + return self.is_done() + + ######################## + def go_back(self): self.wizard().back() def go_next(self): self.wizard().next() - def initializePage(self): - self.clean_errors() - self.clean_wizard_errors() - 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 @@ -313,3 +333,14 @@ class ValidationPage(QtGui.QWizardPage): 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() + self.steps.removeAllSteps() + self.clearTable() + self.resizeTable() + self.timer.singleShot(0, self.do_checks) -- cgit v1.2.3 From 4e1d0ed099a82843cbb91d5f417c552e9f1674e2 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 20 Nov 2012 19:09:11 +0900 Subject: added inline panel within zero-margin frame --- src/leap/gui/firstrun/providerselect.py | 21 +++++++++++++++------ src/leap/gui/progress.py | 20 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index fc030cf3..3f021ee9 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -74,7 +74,7 @@ class SelectProviderPage(InlineValidationPage): validationMsg = QtGui.QLabel("") validationMsg.setStyleSheet(ErrorLabelStyleSheet) self.validationMsg = validationMsg - providerCheckButton = QtGui.QPushButton("check") + providerCheckButton = QtGui.QPushButton("chec&k") self.providerCheckButton = providerCheckButton # cert info @@ -113,7 +113,7 @@ class SelectProviderPage(InlineValidationPage): # add validation frame self.setupValidationFrame() - layout.addWidget(self.valFrame, 4, 1, 4, 2) + layout.addWidget(self.valFrame, 4, 2, 4, 2) self.valFrame.hide() self.setLayout(layout) @@ -135,8 +135,13 @@ class SelectProviderPage(InlineValidationPage): def setupValidationFrame(self): qframe = QtGui.QFrame valFrame = qframe() - valFrame.setFrameStyle(qframe.StyledPanel) # | qframe.Sunken) + valFrame.setFrameStyle(qframe.NoFrame) + # Box | qframe.Plain) + # NoFrame, StyledPanel) | qframe.Sunken) + #valFrame.setContentsMargins(0, 0, 0, 0) valframeLayout = QtGui.QVBoxLayout() + zeros = (0, 0, 0, 0) + valframeLayout.setContentsMargins(*zeros) #dummylabel = QtGui.QLabel('test foo') #valframeLayout.addWidget(dummylabel) @@ -147,12 +152,16 @@ class SelectProviderPage(InlineValidationPage): # check domain def onCheckButtonClicked(self): + print 'check button called....' + self.providerCheckButton.setDisabled(True) + self.valFrame.show() import time time.sleep(1) - self.is_done = True - self.providerCheckButton.setDisabled(True) - self.stepChanged.emit('foo check', 0) + # XXX bug here!... Y U DUPLICATE?!! + self.stepChanged.emit('xxx', 10) + self.stepChanged.emit('end_sentinel', 0) self.valFrame.show() + self.is_done = True self.completeChanged.emit() # cert trust verification diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 3ade28b3..cbfa6194 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -152,11 +152,12 @@ class WithStepsMixIn(object): self.onStepStatusChanged) # slot - #@QtCore.pyqtSlot(QtCore.QString, int) + #@QtCore.pyqtSlot(str, int) def onStepStatusChanged(self, status, progress=None): - import pdb4qt; pdb4qt.set_trace() if status not in ("head_sentinel", "end_sentinel"): self.add_status_line(status) + if status in ("end_sentinel"): + self.check_last_item() if progress and hasattr(self, 'progress'): self.progress.setValue(progress) self.progress.update() @@ -165,6 +166,8 @@ class WithStepsMixIn(object): 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): @@ -216,6 +219,19 @@ class WithStepsMixIn(object): logger.debug('populate table. width=%s' % width) table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) + def check_last_item(self): + """ + mark the last item + as done + """ + index = len(self.steps) + table = self.stepsTableWidget + table.setCellWidget( + index - 1, + ProgressStep.DONE, + ImgWidget(img=CHECKMARK_IMG)) + table.update() + def add_status_line(self, message): index = len(self.steps) step = ProgressStep(message, False, index=index) -- cgit v1.2.3 From 7dceb11bcd4cf552938ccfa02daaf6f902ef385b Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 21 Nov 2012 05:15:06 +0900 Subject: tango icons for checking/checked/failed check status --- src/leap/gui/firstrun/providerselect.py | 64 +++++- src/leap/gui/firstrun/providersetup.py | 2 +- src/leap/gui/mainwindow_rc.py | 367 ++++++++++++++++++++++++-------- src/leap/gui/progress.py | 63 ++++-- 4 files changed, 374 insertions(+), 122 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 3f021ee9..1326ca1d 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -6,13 +6,14 @@ import logging 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.gui.constants import APP_LOGO from leap.gui.progress import InlineValidationPage from leap.gui.styles import ErrorLabelStyleSheet +from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) @@ -38,9 +39,6 @@ class SelectProviderPage(InlineValidationPage): self.setupSteps() self.setupUI() - self.stepChanged.connect( - self.onStepStatusChanged) - def setupUI(self): """ initializes the UI @@ -155,16 +153,59 @@ class SelectProviderPage(InlineValidationPage): print 'check button called....' self.providerCheckButton.setDisabled(True) self.valFrame.show() - import time - time.sleep(1) - # XXX bug here!... Y U DUPLICATE?!! - self.stepChanged.emit('xxx', 10) - self.stepChanged.emit('end_sentinel', 0) - self.valFrame.show() + self.do_checks() + + def _do_checks(self, update_signal=None, failed_signal=None): + """ + executes actual checks in a separate thread + """ + finish = lambda: update_signal.emit("end_sentinel", 100) + + wizard = self.wizard() + prevpage = "providerselection" + + 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) + + update_signal.emit("head_sentinel", 0) + + ######################## + # 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) + failed_signal.emit() + return False + self.is_done = True + finish() + + def _inline_validation_ready(self): + """ + called after _do_checks has finished. + """ + # XXX check if it's really done (catch signal for completed) + #self.done = True self.completeChanged.emit() # cert trust verification + # (disabled for now) def is_insecure_cert_trusted(self): return self.trustProviderCertCheckBox.isChecked() @@ -272,6 +313,9 @@ class SelectProviderPage(InlineValidationPage): 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 0f0bdbd0..7c19dc3f 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -54,7 +54,7 @@ class ProviderSetupValidationPage(ValidationPage): # (so we're able to go back an correct) step = "fetch_eipcert" - update_signal.emit('validating credentials', 20) + update_signal.emit('Validating credentials', 20) unamek = 'login_userName' passwk = 'login_userPassword' 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 cbfa6194..10d19207 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -17,7 +17,9 @@ 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__) @@ -147,17 +149,21 @@ class StepsTableWidget(QtGui.QTableWidget): class WithStepsMixIn(object): def connect_step_status(self): - print 'connect method called' self.stepChanged.connect( self.onStepStatusChanged) + def connect_failstep_status(self): + self.stepFailed.connect( + self.set_failed_icon) + # 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.check_last_item() + self.checks_finished = True + self.set_checked_icon() if progress and hasattr(self, 'progress'): self.progress.setValue(progress) self.progress.update() @@ -219,35 +225,44 @@ class WithStepsMixIn(object): logger.debug('populate table. width=%s' % width) table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) - def check_last_item(self): + 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 - 1, + _index, ProgressStep.DONE, - ImgWidget(img=CHECKMARK_IMG)) + 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 - - # setting cell widget. - # see note on StepsTableWidget about plans to - # change this for a better solution. - - table.setCellWidget( - index - 1, - ProgressStep.DONE, - ImgWidget(img=CHECKMARK_IMG)) - table.update() + self.set_checking_icon() + self.set_checked_icon(current=False) """ @@ -265,10 +280,24 @@ class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): # signals stepChanged = QtCore.pyqtSignal([str, int]) + stepFailed = QtCore.pyqtSignal() def __init__(self, parent=None): super(InlineValidationPage, self).__init__(parent) self.connect_step_status() + self.connect_failstep_status() + + def do_checks(self): + """ + launches a thread to do the checks + """ + beupdate = self.stepChanged + befailed = self.stepFailed + self.checks = FunThread( + self._do_checks(update_signal=beupdate, failed_signal=befailed)) + self.checks.finished.connect(self._inline_validation_ready) + self.checks.begin() + #self.checks.wait() class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): -- cgit v1.2.3 From 8ad4957531f5279cf65df36b015d83443deded33 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 21 Nov 2012 05:55:23 +0900 Subject: update check status when provider entry changes. disable next button and remove any error message (but remember which was the latest bad string if you are changing providers withouth going to the next page). --- src/leap/gui/firstrun/providerselect.py | 40 ++++++++++++++++++++++++--------- src/leap/gui/firstrun/wizard.py | 5 +++++ 2 files changed, 34 insertions(+), 11 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 1326ca1d..b6482379 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -22,10 +22,10 @@ class SelectProviderPage(InlineValidationPage): def __init__(self, parent=None, providers=None): super(SelectProviderPage, self).__init__(parent) - 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, @@ -72,7 +72,7 @@ class SelectProviderPage(InlineValidationPage): validationMsg = QtGui.QLabel("") validationMsg.setStyleSheet(ErrorLabelStyleSheet) self.validationMsg = validationMsg - providerCheckButton = QtGui.QPushButton("chec&k") + providerCheckButton = QtGui.QPushButton(self.tr("chec&k")) self.providerCheckButton = providerCheckButton # cert info @@ -82,7 +82,8 @@ class SelectProviderPage(InlineValidationPage): # 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) @@ -120,7 +121,8 @@ class SelectProviderPage(InlineValidationPage): def setupCertInfoGroup(self): # XXX not used now. - certinfoGroup = QtGui.QGroupBox("Certificate validation") + certinfoGroup = QtGui.QGroupBox( + self.tr("Certificate validation")) certinfoLayout = QtGui.QVBoxLayout() certinfoLayout.addWidget(self.certInfo) certinfoLayout.addWidget(self.certWarning) @@ -150,7 +152,6 @@ class SelectProviderPage(InlineValidationPage): # check domain def onCheckButtonClicked(self): - print 'check button called....' self.providerCheckButton.setDisabled(True) self.valFrame.show() self.do_checks() @@ -180,7 +181,7 @@ class SelectProviderPage(InlineValidationPage): ######################## # 1) try name resolution ######################## - update_signal.emit("Checking that server is reachable", 20) + update_signal.emit(self.tr("Can reach provider"), 20) logger.debug('checking name resolution') try: netchecker.check_name_resolution( @@ -191,6 +192,7 @@ class SelectProviderPage(InlineValidationPage): wizard.set_validation_error( prevpage, exc.usermessage) failed_signal.emit() + self.is_done = False return False self.is_done = True @@ -200,8 +202,9 @@ class SelectProviderPage(InlineValidationPage): """ called after _do_checks has finished. """ - # XXX check if it's really done (catch signal for completed) - #self.done = True + self.domain_checked = True + if self.is_done: + self.wizard().clean_validation_error(self.current_page) self.completeChanged.emit() # cert trust verification @@ -232,6 +235,7 @@ class SelectProviderPage(InlineValidationPage): self.certinfoGroup.show() def onProviderChanged(self, text): + self.is_done = False provider = self.providerNameEdit.text() if provider: self.providerCheckButton.setDisabled(False) @@ -242,8 +246,12 @@ class SelectProviderPage(InlineValidationPage): def reset_validation_status(self): """ empty the validation msg + and clean the inline validation widget. """ self.validationMsg.setText('') + self.steps.removeAllSteps() + self.clearTable() + self.domain_checked = False # pagewizard methods @@ -283,10 +291,19 @@ class SelectProviderPage(InlineValidationPage): showerr(errors) 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) else: - showerr('') + if not getattr(self, 'domain_checked', None): + showerr('') + else: + self.bad_string = cur_str + showerr(errors) def cleanup_errormsg(self): """ @@ -294,6 +311,7 @@ class SelectProviderPage(InlineValidationPage): should be called before leaving the page """ self.bad_string = None + self.domain_checked = False def paintEvent(self, event): """ diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index 8efa2018..4a72177e 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -170,6 +170,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) -- cgit v1.2.3 From 53c6c92e26970de7de0bddca0034e72af7d0ce48 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 21 Nov 2012 06:15:41 +0900 Subject: add red border to failed field --- src/leap/gui/firstrun/providerselect.py | 11 +++++++++-- src/leap/gui/progress.py | 1 + src/leap/gui/styles.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index b6482379..2786c494 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -12,7 +12,7 @@ from leap.base import exceptions as baseexceptions from leap.gui.constants import APP_LOGO from leap.gui.progress import InlineValidationPage -from leap.gui.styles import ErrorLabelStyleSheet +from leap.gui import styles from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) @@ -70,7 +70,7 @@ class SelectProviderPage(InlineValidationPage): #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 @@ -284,11 +284,16 @@ class SelectProviderPage(InlineValidationPage): 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. @@ -298,9 +303,11 @@ class SelectProviderPage(InlineValidationPage): # enter a domain. if cur_str == bad_str: showerr(errors) + markred() else: if not getattr(self, 'domain_checked', None): showerr('') + umarkrd() else: self.bad_string = cur_str showerr(errors) diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 10d19207..33b0cb8c 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -369,6 +369,7 @@ class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): self.checks.begin() #logger.debug('check thread started!') #logger.debug('waiting for it to terminate...') + # XXX needed for it to join? self.checks.wait() def show_progress(self): diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py index 759817ce..18c0ac97 100644 --- a/src/leap/gui/styles.py +++ b/src/leap/gui/styles.py @@ -2,3 +2,20 @@ 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; +} +""" -- cgit v1.2.3 From 7bf4c0aa6db8cbaa1befdb2841f722554a3a0731 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 21 Nov 2012 23:01:06 +0900 Subject: fixed ui freeze using queue for passing status between worker and parent --- src/leap/gui/firstrun/providerinfo.py | 119 +------------------ src/leap/gui/firstrun/providerselect.py | 200 ++++++++++++++++++++++++++++---- src/leap/gui/progress.py | 97 ++++++++++++---- src/leap/gui/threads.py | 8 +- 4 files changed, 265 insertions(+), 159 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index c5f39938..48763357 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -98,120 +98,8 @@ class ProviderInfoPage(ValidationPage): """ executes actual checks in a separate thread """ - finish = lambda: update_signal.emit("end_sentinel", 100) - - def pause_and_finish(): - # only for local debug - finish() - 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 - finish() - return False - - except baseexceptions.LeapException as exc: - wizard.set_validation_error( - prevpage, exc.usermessage) - 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.") - finish() - return False - except requests.exceptions.ConnectionError: - wizard.set_validation_error( - prevpage, - "Could not download provider info " - "(refused conn.).") - finish() - return False - # XXX catch more errors... - # We're done! self.set_done() - finish() def _do_validation(self): """ @@ -247,7 +135,12 @@ class ProviderInfoPage(ValidationPage): self.completeChanged.emit() def cleanupPage(self): - del self.wizard().providerconfig + wizard = self.wizard() + + # XXX makes sense now? + # this was created on previous... + if hasattr(wizard, 'providerconfig'): + del self.wizard().providerconfig if self.infoWidget: QtCore.QObjectCleanupHandler().add( diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 2786c494..08e09ee3 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -1,14 +1,17 @@ """ Select Provider Page, used in First Run Wizard """ +from functools import partial import logging +import requests + from PyQt4 import QtCore from PyQt4 import QtGui 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.constants import APP_LOGO from leap.gui.progress import InlineValidationPage @@ -17,8 +20,24 @@ from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) +# XXX check newer version in progress... + + +def delay(obj, method_str): + """ + 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) @@ -39,6 +58,11 @@ class SelectProviderPage(InlineValidationPage): self.setupSteps() self.setupUI() + #self.disableCheckButton.connect( + #self.onDisableCheckButton) + self.launchChecks.connect( + self.launch_checks) + def setupUI(self): """ initializes the UI @@ -149,21 +173,41 @@ class SelectProviderPage(InlineValidationPage): valFrame.setLayout(valframeLayout) self.valFrame = valFrame - # check domain - - def onCheckButtonClicked(self): + @QtCore.pyqtSlot() + def onDisableCheckButton(self): + print 'CHECK BUTTON DISABLED!!!' self.providerCheckButton.setDisabled(True) - self.valFrame.show() + + @QtCore.pyqtSlot() + def launch_checks(self): + # trying to delay this... + #timer = QtCore.QTimer() + #timer.singleShot(0, self.do_checks) self.do_checks() - def _do_checks(self, update_signal=None, failed_signal=None): + def onCheckButtonClicked(self): + #self.disableCheckButton.emit() + # XXX trying to get responsiveness. + # UI here is blocking, although I'm using + # threads and signals :( + QtCore.QMetaObject.invokeMethod( + self, "onDisableCheckButton") + + QtCore.QMetaObject.invokeMethod( + self, "showStepsFrame") + + delay(self, "launch_checks") + + print 'ON CHECK BUTTON --- DONE!' + print 'timer.....' + + def _do_checks(self): """ executes actual checks in a separate thread """ - finish = lambda: update_signal.emit("end_sentinel", 100) wizard = self.wizard() - prevpage = "providerselection" + curpage = "providerselection" full_domain = self.providerNameEdit.text() @@ -173,35 +217,143 @@ class SelectProviderPage(InlineValidationPage): netchecker = wizard.netchecker() - #providercertchecker = wizard.providercertchecker() - #eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + providercertchecker = wizard.providercertchecker() + eipconfigchecker = wizard.eipconfigchecker(domain=_domain) + + def fail(): + self.is_done = False + return False - update_signal.emit("head_sentinel", 0) + yield(("head_sentinel", 0), lambda: None) ######################## # 1) try name resolution ######################## - update_signal.emit(self.tr("Can reach provider"), 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) - failed_signal.emit() - self.is_done = False - return False + def namecheck(): + try: + netchecker.check_name_resolution( + domain) + + except baseexceptions.LeapException as exc: + logger.error(exc.message) + wizard.set_validation_error( + curpage, exc.usermessage) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + + # XXX catch more exceptions + + yield(("check name", 20), namecheck) + + ######################### + # 2) try https connection + ######################### + + logger.debug('checking https connection') + + def httpscheck(): + 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( + curpage, 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 + return fail() + + except baseexceptions.LeapException as exc: + wizard.set_validation_error( + curpage, exc.usermessage) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + + yield(("https check", 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. + wizard.set_validation_error( + curpage, + self.tr( + "Could not get info from provider.")) + return fail() + except requests.exceptions.ConnectionError: + wizard.set_validation_error( + curpage, + self.tr( + "Could not download provider info " + "(refused conn.).")) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() + + else: + return True + yield(("fetch info", 80), fetchinfo) + + # done! self.is_done = True - finish() + yield(("end_sentinel", 100), lambda: None) def _inline_validation_ready(self): """ called after _do_checks has finished. """ + print 'VALIDATION READY ---------------' self.domain_checked = True if self.is_done: self.wizard().clean_validation_error(self.current_page) diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 33b0cb8c..9a5b352c 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -24,6 +24,24 @@ 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 @@ -168,6 +186,22 @@ class WithStepsMixIn(object): self.progress.setValue(progress) self.progress.update() + def processStepsQueue(self): + """ + consume steps queue + and pass messages + to the ui updater functions + """ + while self.queue.qsize(): + try: + status = self.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 @@ -266,38 +300,59 @@ class WithStepsMixIn(object): """ -Resist the temptation to refactor the declaration of the signal -to the mixin. -PyQt and multiple inheritance do not mix well together. -You can only have one QObject base. -Therefore, we will use one base class for the intermediate pages +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): - # signals - stepChanged = QtCore.pyqtSignal([str, int]) - stepFailed = QtCore.pyqtSignal() +class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): def __init__(self, parent=None): super(InlineValidationPage, self).__init__(parent) - self.connect_step_status() - self.connect_failstep_status() + + self.queue = Queue.Queue() + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.processStepsQueue) + self.timer.start(100) + self.threads = [] def do_checks(self): - """ - launches a thread to do the checks - """ - beupdate = self.stepChanged - befailed = self.stepFailed - self.checks = FunThread( - self._do_checks(update_signal=beupdate, failed_signal=befailed)) - self.checks.finished.connect(self._inline_validation_ready) - self.checks.begin() - #self.checks.wait() + + # yo dawg, I heard you like checks + # so I put a __do_checks in your do_checks + # for calling others' _do_checks + + def __do_checks(fun=None, queue=None): + + for checkcase in fun(): + checkmsg, checkfun = checkcase + + queue.put(checkmsg) + if checkfun() is False: + queue.put("failed") + break + + t = FunThread(fun=partial( + __do_checks, + fun=self._do_checks, + queue=self.queue)) + t.finished.connect(self._inline_validation_ready) + t.begin() + self.threads.append(t) + + # slot + + @QtCore.pyqtSlot() + def showStepsFrame(self): + self.valFrame.show() + self.update() class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): 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() -- cgit v1.2.3 From f9a3e2cf6c4f7398411788974a2a6a9a53c9a1ab Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 22 Nov 2012 02:46:57 +0900 Subject: info page refactor. --- src/leap/gui/firstrun/providerinfo.py | 86 ++++++--------------------------- src/leap/gui/firstrun/providerselect.py | 40 +++++++-------- src/leap/gui/firstrun/providersetup.py | 6 +-- 3 files changed, 36 insertions(+), 96 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerinfo.py b/src/leap/gui/firstrun/providerinfo.py index 48763357..c5b2984c 100644 --- a/src/leap/gui/firstrun/providerinfo.py +++ b/src/leap/gui/firstrun/providerinfo.py @@ -3,43 +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.infoWidget = None - #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() @@ -47,19 +37,24 @@ 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. @@ -94,59 +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 - """ - # We're done! - self.set_done() - - 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.hide_progress() - 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 isComplete(self): - #return self.is_done() - def initializePage(self): - super(ProviderInfoPage, self).initializePage() - self.show_progress() - self.set_undone() - self.completeChanged.emit() - - def cleanupPage(self): - wizard = self.wizard() - - # XXX makes sense now? - # this was created on previous... - if hasattr(wizard, 'providerconfig'): - del self.wizard().providerconfig - - if self.infoWidget: - QtCore.QObjectCleanupHandler().add( - self.infoWidget) - - # refactor this into some kind of destructor - del self.displayName - del self.description - del self.enrollment_policy + self.show_provider_info() diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 08e09ee3..0250ab8c 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -58,8 +58,6 @@ class SelectProviderPage(InlineValidationPage): self.setupSteps() self.setupUI() - #self.disableCheckButton.connect( - #self.onDisableCheckButton) self.launchChecks.connect( self.launch_checks) @@ -160,15 +158,10 @@ class SelectProviderPage(InlineValidationPage): qframe = QtGui.QFrame valFrame = qframe() valFrame.setFrameStyle(qframe.NoFrame) - # Box | qframe.Plain) - # NoFrame, StyledPanel) | qframe.Sunken) - #valFrame.setContentsMargins(0, 0, 0, 0) valframeLayout = QtGui.QVBoxLayout() zeros = (0, 0, 0, 0) valframeLayout.setContentsMargins(*zeros) - #dummylabel = QtGui.QLabel('test foo') - #valframeLayout.addWidget(dummylabel) valframeLayout.addWidget(self.stepsTableWidget) valFrame.setLayout(valframeLayout) self.valFrame = valFrame @@ -180,16 +173,9 @@ class SelectProviderPage(InlineValidationPage): @QtCore.pyqtSlot() def launch_checks(self): - # trying to delay this... - #timer = QtCore.QTimer() - #timer.singleShot(0, self.do_checks) self.do_checks() def onCheckButtonClicked(self): - #self.disableCheckButton.emit() - # XXX trying to get responsiveness. - # UI here is blocking, although I'm using - # threads and signals :( QtCore.QMetaObject.invokeMethod( self, "onDisableCheckButton") @@ -198,12 +184,10 @@ class SelectProviderPage(InlineValidationPage): delay(self, "launch_checks") - print 'ON CHECK BUTTON --- DONE!' - print 'timer.....' - def _do_checks(self): """ - executes actual checks in a separate thread + generator that yields actual checks + that are executed in a separate thread """ wizard = self.wizard() @@ -229,9 +213,13 @@ class SelectProviderPage(InlineValidationPage): ######################## # 1) try name resolution ######################## - logger.debug('checking name resolution') def namecheck(): + """ + in which we check if + we are able to name resolve + this domain + """ try: netchecker.check_name_resolution( domain) @@ -250,17 +238,21 @@ class SelectProviderPage(InlineValidationPage): else: return True - # XXX catch more exceptions + logger.debug('checking name resolution') yield(("check name", 20), namecheck) ######################### # 2) try https connection ######################### - logger.debug('checking https connection') - def httpscheck(): + """ + in which we check + if the provider + is offering service over + https + """ try: providercertchecker.is_https_working( "https://%s" % _domain, @@ -307,6 +299,7 @@ class SelectProviderPage(InlineValidationPage): else: return True + logger.debug('checking https connection') yield(("https check", 40), httpscheck) ################################## @@ -343,9 +336,11 @@ class SelectProviderPage(InlineValidationPage): else: return True + yield(("fetch info", 80), fetchinfo) # done! + self.is_done = True yield(("end_sentinel", 100), lambda: None) @@ -353,7 +348,6 @@ class SelectProviderPage(InlineValidationPage): """ called after _do_checks has finished. """ - print 'VALIDATION READY ---------------' self.domain_checked = True if self.is_done: self.wizard().clean_validation_error(self.current_page) diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 7c19dc3f..042d117c 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -20,9 +20,9 @@ class ProviderSetupValidationPage(ValidationPage): is_signup = self.field("is_signup") self.is_signup = is_signup - self.setTitle("Setting up provider") - #self.setSubTitle( - #"auto configuring provider...") + self.setTitle("Provider setup") + self.setSubTitle( + "Doing autoconfig.") self.setPixmap( QtGui.QWizard.LogoPixmap, -- cgit v1.2.3 From 60ae69dd79fc4a17e54e9f898b04c7130d8b9f6e Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 22 Nov 2012 06:26:02 +0900 Subject: fix widgets focus on providerselect page it works with enter, enter, enter :) dedicated to DJ Focus... --- src/leap/gui/firstrun/login.py | 1 + src/leap/gui/firstrun/providerselect.py | 13 ++++++++++--- src/leap/gui/progress.py | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py index 4271c774..004fa7d4 100644 --- a/src/leap/gui/firstrun/login.py +++ b/src/leap/gui/firstrun/login.py @@ -139,6 +139,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): def initializePage(self): super(LogInPage, self).initializePage() + # XXX setPlaceholderText instead?! self.userNameLineEdit.setText('username@provider.example.org') self.userNameLineEdit.cursorPositionChanged.connect( self.onUserNameEdit) diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index 0250ab8c..d029b1db 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -61,6 +61,9 @@ class SelectProviderPage(InlineValidationPage): self.launchChecks.connect( self.launch_checks) + self.providerNameEdit.editingFinished.connect( + lambda: self.providerCheckButton.setFocus(True)) + def setupUI(self): """ initializes the UI @@ -94,7 +97,7 @@ class SelectProviderPage(InlineValidationPage): validationMsg = QtGui.QLabel("") validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet) self.validationMsg = validationMsg - providerCheckButton = QtGui.QPushButton(self.tr("chec&k")) + providerCheckButton = QtGui.QPushButton(self.tr("chec&k!")) self.providerCheckButton = providerCheckButton # cert info @@ -238,7 +241,6 @@ class SelectProviderPage(InlineValidationPage): else: return True - logger.debug('checking name resolution') yield(("check name", 20), namecheck) @@ -349,9 +351,14 @@ class SelectProviderPage(InlineValidationPage): 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) - self.completeChanged.emit() + nextbutton = self.wizard().button(QtGui.QWizard.NextButton) + nextbutton.setFocus() + else: + self.providerNameEdit.setFocus() # cert trust verification # (disabled for now) diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 9a5b352c..331db6b2 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -145,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 @@ -254,7 +256,7 @@ class WithStepsMixIn(object): 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) -- cgit v1.2.3 From 7a263b8ee74cc92ba39796cd9ad48395adfa7450 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 23 Nov 2012 05:13:36 +0900 Subject: refactor validation mixin; progress until register page --- src/leap/gui/firstrun/login.py | 67 +++++++++++++- src/leap/gui/firstrun/providerselect.py | 8 +- src/leap/gui/firstrun/providersetup.py | 158 ++++++++++++++++---------------- src/leap/gui/firstrun/register.py | 158 +++++++++++++++++++++++++------- src/leap/gui/firstrun/regvalidation.py | 79 ++-------------- src/leap/gui/firstrun/wizard.py | 1 + src/leap/gui/progress.py | 149 +++++++++++++++--------------- 7 files changed, 359 insertions(+), 261 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/login.py b/src/leap/gui/firstrun/login.py index 004fa7d4..3a6ec089 100644 --- a/src/leap/gui/firstrun/login.py +++ b/src/leap/gui/firstrun/login.py @@ -6,14 +6,17 @@ from PyQt4 import QtGui #import requests +from leap.base import auth from leap.gui.firstrun.mixins import UserFormMixIn from leap.gui.constants import APP_LOGO, FULL_USERNAME_REGEX from leap.gui.styles import ErrorLabelStyleSheet -class LogInPage(QtGui.QWizardPage, UserFormMixIn): +class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage + def __init__(self, parent=None): + super(LogInPage, self).__init__(parent) self.setTitle("Log In") @@ -24,6 +27,9 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): QtGui.QWizard.LogoPixmap, QtGui.QPixmap(APP_LOGO)) + self.setupUI() + + def setupUI(self): userNameLabel = QtGui.QLabel("User &name:") userNameLineEdit = QtGui.QLineEdit() userNameLineEdit.cursorPositionChanged.connect( @@ -149,6 +155,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): #wizard = self.wizard() #eipconfigchecker = wizard.eipconfigchecker() + # XXX should move to _do_checks full_username = self.userNameLineEdit.text() password = self.userPasswordLineEdit.text() if full_username.count('@') != 1: @@ -191,3 +198,61 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): self.cleanup_errormsg() return True + + def _do_checks(self): + # XXX convert this to inline + + full_username = self.userNameLineEdit.text() + password = self.userPasswordLineEdit.text() + username, domain = full_username.split('@') + # We try a call to an authenticated + # page here as a mean to catch + # srp authentication errors while + wizard = self.wizard() + pCertChecker = wizard.providercertchecker( + domain=domain) + + curpage = "login" + + def fail(): + self.is_done = False + return False + + ######################## + # 1) try name resolution + ######################## + # XXX + # bring here from validation above... + + ######################## + # 2) do authentication + ######################## + + unamek = 'login_userName' + passwk = 'login_userPassword' + + username = self.field(unamek) + password = self.field(passwk) + credentials = username, password + + def validate_credentials(): + ################# + # FIXME #BUG #638 + verify = False + + try: + pCertChecker.download_new_client_cert( + credentials=credentials, + verify=verify) + + except auth.SRPAuthenticationError as exc: + wizard.set_validation_error( + curpage, "Authentication error: %s" % exc.usermessage) + return fail() + + except Exception as exc: + wizard.set_validation_error( + curpage, "%s" % exc.message) + return fail() + + yield(('Validating credentials', 20), lambda: None) diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index d029b1db..dffde040 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -1,7 +1,6 @@ """ Select Provider Page, used in First Run Wizard """ -from functools import partial import logging import requests @@ -20,10 +19,9 @@ from leap.util.web import get_https_domain_and_port logger = logging.getLogger(__name__) -# XXX check newer version in progress... - def delay(obj, method_str): + # XXX check newer version in progress.py... """ this is a hack to get responsiveness in the ui """ @@ -40,6 +38,7 @@ class SelectProviderPage(InlineValidationPage): def __init__(self, parent=None, providers=None): super(SelectProviderPage, self).__init__(parent) + self.current_page = 'providerselection' self.setTitle(self.tr("Enter Provider")) self.setSubTitle(self.tr( @@ -51,7 +50,6 @@ class SelectProviderPage(InlineValidationPage): QtGui.QPixmap(APP_LOGO)) self.did_cert_check = False - self.current_page = 'providerselection' self.is_done = False @@ -346,7 +344,7 @@ class SelectProviderPage(InlineValidationPage): self.is_done = True yield(("end_sentinel", 100), lambda: None) - def _inline_validation_ready(self): + def on_checks_validation_ready(self): """ called after _do_checks has finished. """ diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 042d117c..7904538d 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__) @@ -20,18 +20,21 @@ class ProviderSetupValidationPage(ValidationPage): is_signup = self.field("is_signup") self.is_signup = is_signup - self.setTitle("Provider setup") + self.setTitle(self.tr("Provider setup")) self.setSubTitle( - "Doing autoconfig.") + 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 """ + curpage = "providersetupvalidation" + full_domain = self.field('provider_domain') wizard = self.wizard() pconfig = wizard.providerconfig @@ -41,68 +44,56 @@ 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) + def fail(): + self.is_done = False + return False - 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) + pCertChecker.download_ca_cert( + uri=ca_cert_uri, + verify=False) - except auth.SRPAuthenticationError as exc: - self.set_error( - step, - "Authentication error: %s" % exc.message) - return False + except baseexceptions.LeapException as exc: + logger.error(exc.message) + wizard.set_validation_error( + curpage, exc.usermessage) + return fail() - #pause_for_user() + except Exception as exc: + wizard.set_validation_error( + curpage, exc.message) + return fail() - ####################################### + else: + return True - update_signal.emit('Fetching CA certificate', 30) - #pause_for_user() + yield(('Fetching CA certificate', 30), fetchcacert) - if pconfig: - ca_cert_uri = pconfig.get('ca_cert_uri').geturl() - else: - ca_cert_uri = None + ######################### + # 2) check CA fingerprint + ######################### - # 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) - - pCertChecker.download_ca_cert( - uri=ca_cert_uri, - verify=False) - #pause_for_user() - - 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,33 +106,40 @@ 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 + 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 - #pause_for_user() - #ca_cert_path = checker.ca_cert_path + yield((self.tr('Validating api certificate'), 90), validatecacert) self.set_done() - update_signal.emit('end_sentinel', 100) - #pause_for_user() + yield(('end_sentinel', 100), lambda: None) - def _do_validation(self): + def on_checks_validation_ready(self): """ called after _do_checks has finished (connected to checker thread finished signal) diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index f872a127..ddfcd1c5 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -2,7 +2,9 @@ Register User Page, used in First Run Wizard """ import logging +import socket +import requests from PyQt4 import QtCore from PyQt4 import QtGui @@ -11,31 +13,34 @@ from leap.gui.firstrun.mixins import UserFormMixIn logger = logging.getLogger(__name__) +from leap.base import auth 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.initUI() + self.setupSteps() + self.setupUI() - def initUI(self): + def setupUI(self): userNameLabel = QtGui.QLabel("User &name:") userNameLineEdit = QtGui.QLineEdit() userNameLineEdit.cursorPositionChanged.connect( @@ -89,8 +94,18 @@ 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) + # change "commit" button text + self.setButtonText( + QtGui.QWizard.CommitButton, "Sign up!") + # pagewizard methods def populateErrors(self): @@ -138,9 +153,6 @@ 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): """ we only pre-validate here password weakness @@ -150,33 +162,117 @@ class RegisterUserPage(QtGui.QWizardPage, UserFormMixIn): and if any errors are thrown there we come back and re-display the validation label. """ + # calls checks, which after successful + # execution will call on_checks_validation_ready + self.do_checks() + return self.is_done() + + def _do_checks(self): + """ + generator that yields actual checks + that are executed in a separate thread + """ + wizard = self.wizard() + curpage = self.current_page + senderr = lambda err: wizard.set_validation_error(curpage, err) - #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. - - if password != password2: - self.set_prevalidation_error('Password does not match.') + def fail(): + self.set_undone() return False - if len(password) < 6: - self.set_prevalidation_error('Password too short.') - return False - - if password == "123456": - # joking, but not too much. - self.set_prevalidation_error('Password too obvious.') - return False - - # some cleanup before we leave the page - self.cleanup_errormsg() - - # go - return True + def checkpass(): + # we better have here + # some call to a password checker... + # to assess strenght and avoid silly stuff. + + if password != password2: + msg = self.tr('Password does not match..') + senderr(msg) + return fail() + + if len(password) < 6: + #self.set_prevalidation_error('Password too short.') + msg = self.tr('Password too short.') + senderr(msg) + return fail() + + if password == "123456": + # joking, but not too much. + #self.set_prevalidation_error('Password too obvious.') + msg = self.tr('Password too obvious.') + senderr(msg) + return fail() + + # go + return True + + yield(("head_sentinel", 0), checkpass) + + # XXX should emit signal for .show the frame! + # XXX HERE! + + ################################################## + # 1) register user + ################################################## + + 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: + msg = self.tr("Error connecting to provider (timeout)") + senderr(msg) + return fail() + + except requests.exceptions.ConnectionError as exc: + logger.error(exc.message) + msg = self.tr('Error Connecting to provider (connerr).') + senderr(msg) + return fail() + + # XXX check for != OK instead??? + + if req.status_code in (404, 500): + msg = self.tr( + "Error during registration (%s)") % req.status_code + return fail() + + 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) + msg = self.tr('Username not available.') + senderr(msg) + return fail() + + logger.debug('registering user') + yield(("registering with provider", 40), register) + + # set_done?? + self.set_done() + yield(("end_sentinel", 0), lambda: None) + + def on_checks_validation_ready(self): + + if self.is_done(): + self.cleanup_errormsg() + self.go_next() def initializePage(self): """ @@ -184,7 +280,7 @@ 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('') diff --git a/src/leap/gui/firstrun/regvalidation.py b/src/leap/gui/firstrun/regvalidation.py index 6db2bf6e..79971944 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -20,7 +20,7 @@ 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__) @@ -77,78 +77,8 @@ class RegisterUserValidationPage(ValidationPage): pCertChecker = wizard.providercertchecker( domain=full_domain) - ########################################### - # only if from signup - # MOVE TO SIGNUP PAGE... - if is_signup: - signup = auth.LeapSRPRegister( - schema="https", - provider=full_domain, - verify=verify) - update_signal.emit("head_sentinel", 0) - ################################################## - # 1) register user - ################################################## - # only if from signup. - # XXX MOVE THIS STEP TO SIGNUP-IN-PLACE VALIDATION - # WIDGET.......................................... - - if is_signup: - - step = "register" - update_signal.emit("checking availability", 20) - update_signal.emit("registering with provider", 40) - logger.debug('registering user') - - 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() - ################################################## # 2) fetching eip service config ################################################## @@ -202,6 +132,13 @@ class RegisterUserValidationPage(ValidationPage): # this should be called CONNECT PAGE AGAIN. self.run_eip_checks_for_provider_and_connect(_domain) + def on_checks_validation_ready(self): + """ + called after _do_checks has finished + (connected to checker thread finished signal) + """ + pass + def run_eip_checks_for_provider_and_connect(self, domain): wizard = self.wizard() conductor = wizard.conductor diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index 4a72177e..2ee1947a 100755 --- a/src/leap/gui/firstrun/wizard.py +++ b/src/leap/gui/firstrun/wizard.py @@ -139,6 +139,7 @@ class FirstRunWizard(QtGui.QWizard): # set options self.setOption(QtGui.QWizard.IndependentPages, on=False) + self.setOption(QtGui.QWizard.NoBackButtonOnStartPage, on=True) self.setWindowTitle("First Run Wizard") diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 331db6b2..4f3a7d81 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -168,13 +168,43 @@ class StepsTableWidget(QtGui.QTableWidget): class WithStepsMixIn(object): - def connect_step_status(self): - self.stepChanged.connect( - self.onStepStatusChanged) + # worker threads for checks + + 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 connect_failstep_status(self): - self.stepFailed.connect( - self.set_failed_icon) + def do_checks(self): + + # yo dawg, I heard you like checks + # so I put a __do_checks in your do_checks + # for calling others' _do_checks + + def __do_checks(fun=None, queue=None): + + for checkcase in fun(): + checkmsg, checkfun = checkcase + + queue.put(checkmsg) + if checkfun() is False: + queue.put("failed") + break + + 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) + + @QtCore.pyqtSlot() + def launch_checks(self): + self.do_checks() # slot #@QtCore.pyqtSlot(str, int) @@ -194,9 +224,9 @@ class WithStepsMixIn(object): and pass messages to the ui updater functions """ - while self.queue.qsize(): + while self.steps_queue.qsize(): try: - status = self.queue.get(0) + status = self.steps_queue.get(0) if status == "failed": self.set_failed_icon() else: @@ -300,6 +330,26 @@ class WithStepsMixIn(object): self.set_checking_icon() self.set_checked_icon(current=False) + # Sets/unsets done flag + # for isComplete checks + + 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() + + def go_next(self): + self.wizard().next() + """ We will use one base class for the intermediate pages @@ -318,36 +368,8 @@ class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): def __init__(self, parent=None): super(InlineValidationPage, self).__init__(parent) - - self.queue = Queue.Queue() - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.processStepsQueue) - self.timer.start(100) - self.threads = [] - - def do_checks(self): - - # yo dawg, I heard you like checks - # so I put a __do_checks in your do_checks - # for calling others' _do_checks - - def __do_checks(fun=None, queue=None): - - for checkcase in fun(): - checkmsg, checkfun = checkcase - - queue.put(checkmsg) - if checkfun() is False: - queue.put("failed") - break - - t = FunThread(fun=partial( - __do_checks, - fun=self._do_checks, - queue=self.queue)) - t.finished.connect(self._inline_validation_ready) - t.begin() - self.threads.append(t) + self.setupStepsProcessingQueue() + self.done = False # slot @@ -356,6 +378,20 @@ class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): 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): """ @@ -376,7 +412,7 @@ class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): def __init__(self, parent=None): super(ValidationPage, self).__init__(parent) self.setupSteps() - self.connect_step_status() + #self.connect_step_status() layout = QtGui.QVBoxLayout() self.progress = QtGui.QProgressBar(self) @@ -387,48 +423,15 @@ class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): self.layout = layout self.timer = QtCore.QTimer() - - self.done = False - - # Sets/unsets done flag - # for isComplete checks - - 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 + self.setupStepsProcessingQueue() def isComplete(self): return self.is_done() ######################## - def go_back(self): - self.wizard().back() - - def go_next(self): - self.wizard().next() - - 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...') - # XXX needed for it to join? - self.checks.wait() - def show_progress(self): self.progress.show() self.stepsTableWidget.show() -- cgit v1.2.3 From d5136a5f3b2aa8b16e8341f2eb99d05993028acf Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 27 Nov 2012 00:12:22 +0900 Subject: inline validation at register page. inline widget and focus and red marks and whistles. --- src/leap/gui/firstrun/providerselect.py | 58 ++++------- src/leap/gui/firstrun/providersetup.py | 23 ++-- src/leap/gui/firstrun/register.py | 179 +++++++++++++++++++++++--------- src/leap/gui/firstrun/regvalidation.py | 80 +++++++------- src/leap/gui/firstrun/wizard.py | 2 +- src/leap/gui/progress.py | 14 +++ src/leap/gui/styles.py | 13 +-- 7 files changed, 212 insertions(+), 157 deletions(-) (limited to 'src/leap/gui') diff --git a/src/leap/gui/firstrun/providerselect.py b/src/leap/gui/firstrun/providerselect.py index dffde040..e59a23a9 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -169,7 +169,7 @@ class SelectProviderPage(InlineValidationPage): @QtCore.pyqtSlot() def onDisableCheckButton(self): - print 'CHECK BUTTON DISABLED!!!' + #print 'CHECK BUTTON DISABLED!!!' self.providerCheckButton.setDisabled(True) @QtCore.pyqtSlot() @@ -183,6 +183,8 @@ class SelectProviderPage(InlineValidationPage): 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): @@ -192,8 +194,6 @@ class SelectProviderPage(InlineValidationPage): """ wizard = self.wizard() - curpage = "providerselection" - full_domain = self.providerNameEdit.text() # we check if we have a port in the domain string. @@ -205,10 +205,6 @@ class SelectProviderPage(InlineValidationPage): providercertchecker = wizard.providercertchecker() eipconfigchecker = wizard.eipconfigchecker(domain=_domain) - def fail(): - self.is_done = False - return False - yield(("head_sentinel", 0), lambda: None) ######################## @@ -227,20 +223,16 @@ class SelectProviderPage(InlineValidationPage): except baseexceptions.LeapException as exc: logger.error(exc.message) - wizard.set_validation_error( - curpage, exc.usermessage) - return fail() + return self.fail(exc.usermessage) except Exception as exc: - wizard.set_validation_error( - curpage, exc.message) - return fail() + return self.fail(exc.message) else: return True logger.debug('checking name resolution') - yield(("check name", 20), namecheck) + yield((self.tr("checking domain name"), 20), namecheck) ######################### # 2) try https connection @@ -260,6 +252,7 @@ class SelectProviderPage(InlineValidationPage): except eipexceptions.HttpsBadCertError as exc: logger.debug('exception') + return self.fail(exc.usermessage) # XXX skipping for now... ############################################## # We had this validation logic @@ -268,8 +261,6 @@ class SelectProviderPage(InlineValidationPage): #if self.trustProviderCertCheckBox.isChecked(): #pass #else: - wizard.set_validation_error( - curpage, exc.usermessage) #fingerprint = certs.get_cert_fingerprint( #domain=domain, sep=" ") @@ -284,23 +275,18 @@ class SelectProviderPage(InlineValidationPage): #self.did_cert_check = True #self.completeChanged.emit() #return False - return fail() except baseexceptions.LeapException as exc: - wizard.set_validation_error( - curpage, exc.usermessage) - return fail() + return self.fail(exc.usermessage) except Exception as exc: - wizard.set_validation_error( - curpage, exc.message) - return fail() + return self.fail(exc.message) else: return True logger.debug('checking https connection') - yield(("https check", 40), httpscheck) + yield((self.tr("checking https connection"), 40), httpscheck) ################################## # 3) try download provider info... @@ -316,28 +302,20 @@ class SelectProviderPage(InlineValidationPage): except requests.exceptions.SSLError: # XXX we should have catched this before. # but cert checking is broken. - wizard.set_validation_error( - curpage, - self.tr( - "Could not get info from provider.")) - return fail() + return self.fail(self.tr( + "Could not get info from provider.")) except requests.exceptions.ConnectionError: - wizard.set_validation_error( - curpage, - self.tr( - "Could not download provider info " - "(refused conn.).")) - return fail() + return self.fail(self.tr( + "Could not download provider info " + "(refused conn.).")) except Exception as exc: - wizard.set_validation_error( - curpage, exc.message) - return fail() - + return self.fail( + self.tr(exc.message)) else: return True - yield(("fetch info", 80), fetchinfo) + yield((self.tr("fetching provider info"), 80), fetchinfo) # done! diff --git a/src/leap/gui/firstrun/providersetup.py b/src/leap/gui/firstrun/providersetup.py index 7904538d..1a362794 100644 --- a/src/leap/gui/firstrun/providersetup.py +++ b/src/leap/gui/firstrun/providersetup.py @@ -17,6 +17,9 @@ 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 @@ -33,7 +36,6 @@ class ProviderSetupValidationPage(ValidationPage): generator that yields actual checks that are executed in a separate thread """ - curpage = "providersetupvalidation" full_domain = self.field('provider_domain') wizard = self.wizard() @@ -44,10 +46,6 @@ class ProviderSetupValidationPage(ValidationPage): pCertChecker = wizard.providercertchecker( domain=full_domain) - def fail(): - self.is_done = False - return False - yield(("head_sentinel", 0), lambda: None) ######################## @@ -73,19 +71,17 @@ class ProviderSetupValidationPage(ValidationPage): except baseexceptions.LeapException as exc: logger.error(exc.message) - wizard.set_validation_error( - curpage, exc.usermessage) - return fail() + # XXX this should be _ method + return self.fail(self.tr(exc.usermessage)) except Exception as exc: - wizard.set_validation_error( - curpage, exc.message) - return fail() + return self.fail(exc.message) else: return True - yield(('Fetching CA certificate', 30), fetchcacert) + yield((self.tr('Fetching CA certificate'), 30), + fetchcacert) ######################### # 2) check CA fingerprint @@ -106,7 +102,8 @@ class ProviderSetupValidationPage(ValidationPage): # should catch exception #return False - yield((self.tr("Checking CA fingerprint"), 60), checkcafingerprint) + yield((self.tr("Checking CA fingerprint"), 60), + checkcafingerprint) ######################### # 2) check CA fingerprint diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index ddfcd1c5..7ce74892 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -1,6 +1,7 @@ """ Register User Page, used in First Run Wizard """ +import json import logging import socket @@ -14,6 +15,7 @@ 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 @@ -39,6 +41,8 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): self.setupSteps() self.setupUI() + self.do_confirm_next = False + self.focused_field = False def setupUI(self): userNameLabel = QtGui.QLabel("User &name:") @@ -101,23 +105,64 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): self.valFrame.hide() self.setLayout(layout) + self.commitText("Sign up!") + # commit button + + def commitText(self, text): # change "commit" button text self.setButtonText( - QtGui.QWizard.CommitButton, "Sign up!") + QtGui.QWizard.CommitButton, text) - # pagewizard methods + @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) if bad_str is None: @@ -133,7 +178,13 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): 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): """ @@ -153,60 +204,32 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): super(RegisterUserPage, self).paintEvent(event) self.populateErrors() - def validatePage(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. - """ - # calls checks, which after successful - # execution will call on_checks_validation_ready - self.do_checks() - return self.is_done() - def _do_checks(self): """ generator that yields actual checks that are executed in a separate thread """ - wizard = self.wizard() - curpage = self.current_page - senderr = lambda err: wizard.set_validation_error(curpage, err) - provider = self.field('provider_domain') username = self.userNameLineEdit.text() password = self.userPasswordLineEdit.text() password2 = self.userPassword2LineEdit.text() - def fail(): - self.set_undone() - return False - def checkpass(): # we better have here # some call to a password checker... # to assess strenght and avoid silly stuff. if password != password2: - msg = self.tr('Password does not match..') - senderr(msg) - return fail() + return self.fail(self.tr('Password does not match..')) if len(password) < 6: #self.set_prevalidation_error('Password too short.') - msg = self.tr('Password too short.') - senderr(msg) - return fail() + return self.fail(self.tr('Password too short.')) if password == "123456": # joking, but not too much. #self.set_prevalidation_error('Password too obvious.') - msg = self.tr('Password too obvious.') - senderr(msg) - return fail() + return self.fail(self.tr('Password too obvious.')) # go return True @@ -220,6 +243,10 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): # 1) register user ################################################## + # show the frame before going on... + QtCore.QMetaObject.invokeMethod( + self, "showStepsFrame") + def register(): # XXX FIXME! verify = False @@ -233,22 +260,22 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): username, password) except socket.timeout: - msg = self.tr("Error connecting to provider (timeout)") - senderr(msg) - return fail() + return self.fail( + self.tr("Error connecting to provider (timeout)")) except requests.exceptions.ConnectionError as exc: logger.error(exc.message) - msg = self.tr('Error Connecting to provider (connerr).') - senderr(msg) - return fail() + 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): - msg = self.tr( - "Error during registration (%s)") % req.status_code - return fail() + return self.fail( + self.tr( + "Error during registration (%s)") % req.status_code) validation_msgs = json.loads(req.content) errors = validation_msgs.get('errors', None) @@ -257,9 +284,8 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): if errors and errors.get('login', None): # XXX this sometimes catch the blank username # but we're not allowing that (soon) - msg = self.tr('Username not available.') - senderr(msg) - return fail() + return self.fail( + self.tr('Username not available.')) logger.debug('registering user') yield(("registering with provider", 40), register) @@ -269,10 +295,61 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): yield(("end_sentinel", 0), lambda: None) def on_checks_validation_ready(self): - + """ + after checks + """ if self.is_done(): + # XXX should disable + # all entry forms + self.disableFields() self.cleanup_errormsg() - self.go_next() + 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() + + return self.is_done() def initializePage(self): """ @@ -284,9 +361,11 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): 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 79971944..0e67834b 100644 --- a/src/leap/gui/firstrun/regvalidation.py +++ b/src/leap/gui/firstrun/regvalidation.py @@ -9,12 +9,12 @@ 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 @@ -77,67 +77,59 @@ class RegisterUserValidationPage(ValidationPage): pCertChecker = wizard.providercertchecker( domain=full_domain) - update_signal.emit("head_sentinel", 0) + yield(("head_sentinel", 0), lambda: None) ################################################## - # 2) fetching eip service config + # 1) fetching eip service config ################################################## + def fetcheipconf(): + try: + eipconfigchecker.fetch_eip_service_config( + domain=full_domain) - 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() + # XXX get specific exception + except Exception as exc: + return self.fail(exc.message) + + 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 ! ################ - - update_signal.emit("end_sentinel", 100) - #pause_for_user() - - # here we go! :) - # this should be called CONNECT PAGE AGAIN. - self.run_eip_checks_for_provider_and_connect(_domain) + 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) """ - pass + # 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): wizard = self.wizard() diff --git a/src/leap/gui/firstrun/wizard.py b/src/leap/gui/firstrun/wizard.py index 2ee1947a..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? diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 4f3a7d81..6f13a1ac 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -202,6 +202,20 @@ class WithStepsMixIn(object): t.begin() self.threads.append(t) + 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() diff --git a/src/leap/gui/styles.py b/src/leap/gui/styles.py index 18c0ac97..b482922e 100644 --- a/src/leap/gui/styles.py +++ b/src/leap/gui/styles.py @@ -1,19 +1,14 @@ -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; }""" -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; -- cgit v1.2.3 From 1bb7e85425f2f427401cd02726c55922874a59a0 Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 27 Nov 2012 03:34:08 +0900 Subject: login validation inline --- src/leap/gui/firstrun/last.py | 2 + src/leap/gui/firstrun/login.py | 242 +++++++++++++++++++++----------- src/leap/gui/firstrun/providerselect.py | 4 +- src/leap/gui/firstrun/register.py | 11 +- 4 files changed, 165 insertions(+), 94 deletions(-) (limited to 'src/leap/gui') 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 3a6ec089..02bace86 100644 --- a/src/leap/gui/firstrun/login.py +++ b/src/leap/gui/firstrun/login.py @@ -4,20 +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): # InlineValidationPage +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.") @@ -27,8 +29,11 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage 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() @@ -40,6 +45,9 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage 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:") @@ -55,7 +63,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage layout.setColumnMinimumWidth(0, 20) validationMsg = QtGui.QLabel("") - validationMsg.setStyleSheet(ErrorLabelStyleSheet) + validationMsg.setStyleSheet(styles.ErrorLabelStyleSheet) self.validationMsg = validationMsg layout.addWidget(validationMsg, 0, 3) @@ -64,18 +72,38 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage 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 @@ -83,13 +111,13 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage 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() @@ -100,13 +128,14 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage 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): @@ -130,7 +159,7 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage def set_prevalidation_error(self, error): self.prevalidation_error = error - #### end possible refactor + # pagewizard methods def nextId(self): wizard = self.wizard() @@ -145,95 +174,115 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage def initializePage(self): super(LogInPage, self).initializePage() - # XXX setPlaceholderText instead?! - 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 validatePage(self): - #wizard = self.wizard() - #eipconfigchecker = wizard.eipconfigchecker() + def reset_validation_status(self): + """ + empty the validation msg + and clean the inline validation widget. + """ + self.validationMsg.setText('') + self.steps.removeAllSteps() + self.clearTable() - # XXX should move to _do_checks - 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 + def validatePage(self): + """ + if not register done, do checks. + if done, wait for click. + """ + self.disableNextButton() + self.cleanup_errormsg() + self.clean_wizard_errors(self.current_page) - 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 - #################################################### - # 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) - #################################################### + 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) - # XXX I think this is not needed - # since we're also checking for the is_signup field. - self.wizard().from_login = True + return True - # some cleanup before we leave the page - self.cleanup_errormsg() + if not self.is_done(): + self.reset_validation_status() + self.do_checks() - return True + return self.is_done() def _do_checks(self): # XXX convert this to inline full_username = self.userNameLineEdit.text() - password = self.userPasswordLineEdit.text() + ########################### + # 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('@') + 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() - pCertChecker = wizard.providercertchecker( - domain=domain) - - curpage = "login" - - def fail(): - self.is_done = False - return False + eipconfigchecker = wizard.eipconfigchecker() ######################## # 1) try name resolution ######################## - # XXX - # bring here from validation above... + # show the frame before going on... + QtCore.QMetaObject.invokeMethod( + self, "showStepsFrame") + + # Able to contact domain? + # can get definition? + # two-by-one + 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 ######################## - - unamek = 'login_userName' - passwk = 'login_userPassword' - - username = self.field(unamek) - password = self.field(passwk) credentials = username, password + pCertChecker = wizard.providercertchecker( + domain=domain) def validate_credentials(): ################# @@ -246,13 +295,36 @@ class LogInPage(QtGui.QWizardPage, UserFormMixIn): # InlineValidationPage verify=verify) except auth.SRPAuthenticationError as exc: - wizard.set_validation_error( - curpage, "Authentication error: %s" % exc.usermessage) - return fail() + return self.fail( + self.tr("Authentication error: %s" % exc.message)) except Exception as exc: - wizard.set_validation_error( - curpage, "%s" % exc.message) - return fail() + return self.fail(exc.message) - yield(('Validating credentials', 20), lambda: None) + else: + return True + + 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) + + 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/providerselect.py b/src/leap/gui/firstrun/providerselect.py index e59a23a9..3ffc6ff6 100644 --- a/src/leap/gui/firstrun/providerselect.py +++ b/src/leap/gui/firstrun/providerselect.py @@ -11,12 +11,12 @@ from PyQt4 import QtGui from leap.base import exceptions as baseexceptions #from leap.crypto import certs from leap.eip import exceptions as eipexceptions - -from leap.gui.constants import APP_LOGO 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 + logger = logging.getLogger(__name__) diff --git a/src/leap/gui/firstrun/register.py b/src/leap/gui/firstrun/register.py index 7ce74892..e85723cb 100644 --- a/src/leap/gui/firstrun/register.py +++ b/src/leap/gui/firstrun/register.py @@ -163,7 +163,7 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): if errors: bad_str = getattr(self, 'bad_string', None) cur_str = self.userNameLineEdit.text() - prev_er = getattr(self, 'prevalidation_error', None) + #prev_er = getattr(self, 'prevalidation_error', None) if bad_str is None: # first time we fall here. @@ -171,9 +171,9 @@ class RegisterUserPage(InlineValidationPage, 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) @@ -290,7 +290,6 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): logger.debug('registering user') yield(("registering with provider", 40), register) - # set_done?? self.set_done() yield(("end_sentinel", 0), lambda: None) @@ -299,8 +298,6 @@ class RegisterUserPage(InlineValidationPage, UserFormMixIn): after checks """ if self.is_done(): - # XXX should disable - # all entry forms self.disableFields() self.cleanup_errormsg() self.clean_wizard_errors(self.current_page) -- cgit v1.2.3 From 3ea766452e3c4708c724509d03001c0a0314fcf6 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 28 Nov 2012 01:07:31 +0900 Subject: modified hash of resources --- src/leap/gui/test_mainwindow_rc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui') 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') -- cgit v1.2.3