diff options
author | kali <kali@leap.se> | 2012-11-28 01:11:05 +0900 |
---|---|---|
committer | kali <kali@leap.se> | 2012-11-28 01:11:05 +0900 |
commit | 16f19a225a922dd77f3f6c75c94194ebd229fc67 (patch) | |
tree | d77ba10cdad970b5025e75daa2b3be24e35ce0c7 /src/leap/gui/progress.py | |
parent | 862014f68fce37318f77309a8f8f9782dabc60d2 (diff) | |
parent | 3ea766452e3c4708c724509d03001c0a0314fcf6 (diff) |
Merge branch 'feature/wizard-usability' into develop
Diffstat (limited to 'src/leap/gui/progress.py')
-rw-r--r-- | src/leap/gui/progress.py | 299 |
1 files changed, 239 insertions, 60 deletions
diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 6e8abc1f..6f13a1ac 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -17,11 +17,31 @@ from leap.gui.threads import FunThread from leap.gui import mainwindow_rc -CHECKMARK_IMG = ":/images/checked.png" +ICON_CHECKMARK = ":/images/Dialog-accept.png" +ICON_FAILED = ":/images/Dialog-error.png" +ICON_WAITING = ":/images/Emblem-question.png" logger = logging.getLogger(__name__) +# XXX import this from threads +def delay(obj, method_str=None, call_args=None): + """ + this is a hack to get responsiveness in the ui + """ + if callable(obj) and not method_str: + QtCore.QTimer().singleShot( + 50, + lambda: obj()) + return + + if method_str: + QtCore.QTimer().singleShot( + 50, + lambda: QtCore.QMetaObject.invokeMethod( + obj, method_str)) + + class ImgWidget(QtGui.QWidget): # XXX move to widgets @@ -125,6 +145,8 @@ class StepsTableWidget(QtGui.QTableWidget): # this disables the table grid. # we should add alignment to the ImgWidget (it's top-left now) self.setShowGrid(False) + self.setFocusPolicy(QtCore.Qt.NoFocus) + #self.setStyleSheet("QTableView{outline: 0;}") # XXX change image for done to rc @@ -144,46 +166,94 @@ class StepsTableWidget(QtGui.QTableWidget): # some failing tests if they are not critical. -class ValidationPage(QtGui.QWizardPage): - """ - class to be used as an intermediate - between two pages in a wizard. - shows feedback to the user and goes back if errors, - goes forward if ok. - initializePage triggers a one shot timer - that calls do_checks. - Derived classes should implement - _do_checks and - _do_validation - """ +class WithStepsMixIn(object): - # signals + # worker threads for checks - stepChanged = QtCore.pyqtSignal([str, int]) + def setupStepsProcessingQueue(self): + self.steps_queue = Queue.Queue() + self.stepscheck_timer = QtCore.QTimer() + self.stepscheck_timer.timeout.connect(self.processStepsQueue) + self.stepscheck_timer.start(100) + # we need to keep a reference to child threads + self.threads = [] - def __init__(self, parent=None): - super(ValidationPage, self).__init__(parent) + def do_checks(self): - self.steps = ProgressStepContainer() - self.progress = QtGui.QProgressBar(self) + # yo dawg, I heard you like checks + # so I put a __do_checks in your do_checks + # for calling others' _do_checks - # steps table widget - self.stepsTableWidget = StepsTableWidget(self) + def __do_checks(fun=None, queue=None): - layout = QtGui.QVBoxLayout() - layout.addWidget(self.progress) - layout.addWidget(self.stepsTableWidget) + for checkcase in fun(): + checkmsg, checkfun = checkcase - self.setLayout(layout) - self.layout = layout + queue.put(checkmsg) + if checkfun() is False: + queue.put("failed") + break - self.timer = QtCore.QTimer() + t = FunThread(fun=partial( + __do_checks, + fun=self._do_checks, + queue=self.steps_queue)) + t.finished.connect(self.on_checks_validation_ready) + t.begin() + self.threads.append(t) - # connect the new step status - # signal to status handler - self.stepChanged.connect( - self.onStepStatusChanged) + def fail(self, err=None): + """ + return failed state + and send error notification as + a nice side effect + """ + wizard = self.wizard() + senderr = lambda err: wizard.set_validation_error( + self.current_page, err) + self.set_undone() + if err: + senderr(err) + return False + + @QtCore.pyqtSlot() + def launch_checks(self): + self.do_checks() + + # slot + #@QtCore.pyqtSlot(str, int) + def onStepStatusChanged(self, status, progress=None): + if status not in ("head_sentinel", "end_sentinel"): + self.add_status_line(status) + if status in ("end_sentinel"): + self.checks_finished = True + self.set_checked_icon() + if progress and hasattr(self, 'progress'): + self.progress.setValue(progress) + self.progress.update() + def processStepsQueue(self): + """ + consume steps queue + and pass messages + to the ui updater functions + """ + while self.steps_queue.qsize(): + try: + status = self.steps_queue.get(0) + if status == "failed": + self.set_failed_icon() + else: + self.onStepStatusChanged(*status) + except Queue.Empty: + pass + + def setupSteps(self): + self.steps = ProgressStepContainer() + # steps table widget + self.stepsTableWidget = StepsTableWidget(self) + zeros = (0, 0, 0, 0) + self.stepsTableWidget.setContentsMargins(*zeros) self.errors = OrderedDict() def set_error(self, name, error): @@ -230,34 +300,63 @@ class ValidationPage(QtGui.QWizardPage): def resizeTable(self): # resize first column to ~80% table = self.stepsTableWidget - FIRST_COLUMN_PERCENT = 0.75 + FIRST_COLUMN_PERCENT = 0.70 width = table.width() logger.debug('populate table. width=%s' % width) table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) - def onStepStatusChanged(self, status, progress=None): - if status not in ("head_sentinel", "end_sentinel"): - self.add_status_line(status) - if progress: - self.progress.setValue(progress) - self.progress.update() + def set_item_icon(self, img=ICON_CHECKMARK, current=True): + """ + mark the last item + as done + """ + # setting cell widget. + # see note on StepsTableWidget about plans to + # change this for a better solution. + index = len(self.steps) + table = self.stepsTableWidget + _index = index - 1 if current else index - 2 + table.setCellWidget( + _index, + ProgressStep.DONE, + ImgWidget(img=img)) + table.update() + + def set_failed_icon(self): + self.set_item_icon(img=ICON_FAILED, current=True) + + def set_checking_icon(self): + self.set_item_icon(img=ICON_WAITING, current=True) + + def set_checked_icon(self, current=True): + self.set_item_icon(current=current) def add_status_line(self, message): + """ + adds a new status line + and mark the next-to-last item + as done + """ index = len(self.steps) step = ProgressStep(message, False, index=index) self.steps.addStep(step) self.populateStepsTable() - table = self.stepsTableWidget + self.set_checking_icon() + self.set_checked_icon(current=False) - # setting cell widget. - # see note on StepsTableWidget about plans to - # change this for a better solution. + # Sets/unsets done flag + # for isComplete checks - table.setCellWidget( - index - 1, - ProgressStep.DONE, - ImgWidget(img=CHECKMARK_IMG)) - table.update() + def set_done(self): + self.done = True + self.completeChanged.emit() + + def set_undone(self): + self.done = False + self.completeChanged.emit() + + def is_done(self): + return self.done def go_back(self): self.wizard().back() @@ -265,6 +364,99 @@ class ValidationPage(QtGui.QWizardPage): def go_next(self): self.wizard().next() + +""" +We will use one base class for the intermediate pages +and another one for the in-page validations, both sharing the creation +of the tablewidgets. +The logic of this split comes from where I was trying to solve +the ui update using signals, but now that it's working well with +queues I could join them again. +""" + +import Queue +from functools import partial + + +class InlineValidationPage(QtGui.QWizardPage, WithStepsMixIn): + + def __init__(self, parent=None): + super(InlineValidationPage, self).__init__(parent) + self.setupStepsProcessingQueue() + self.done = False + + # slot + + @QtCore.pyqtSlot() + def showStepsFrame(self): + self.valFrame.show() + self.update() + + # progress frame + + def setupValidationFrame(self): + qframe = QtGui.QFrame + valFrame = qframe() + valFrame.setFrameStyle(qframe.NoFrame) + valframeLayout = QtGui.QVBoxLayout() + zeros = (0, 0, 0, 0) + valframeLayout.setContentsMargins(*zeros) + + valframeLayout.addWidget(self.stepsTableWidget) + valFrame.setLayout(valframeLayout) + self.valFrame = valFrame + + +class ValidationPage(QtGui.QWizardPage, WithStepsMixIn): + """ + class to be used as an intermediate + between two pages in a wizard. + shows feedback to the user and goes back if errors, + goes forward if ok. + initializePage triggers a one shot timer + that calls do_checks. + Derived classes should implement + _do_checks and + _do_validation + """ + + # signals + stepChanged = QtCore.pyqtSignal([str, int]) + + def __init__(self, parent=None): + super(ValidationPage, self).__init__(parent) + self.setupSteps() + #self.connect_step_status() + + layout = QtGui.QVBoxLayout() + self.progress = QtGui.QProgressBar(self) + layout.addWidget(self.progress) + layout.addWidget(self.stepsTableWidget) + + self.setLayout(layout) + self.layout = layout + + self.timer = QtCore.QTimer() + self.done = False + + self.setupStepsProcessingQueue() + + def isComplete(self): + return self.is_done() + + ######################## + + def show_progress(self): + self.progress.show() + self.stepsTableWidget.show() + + def hide_progress(self): + self.progress.hide() + self.stepsTableWidget.hide() + + # pagewizard methods. + # if overriden, child classes should call super. + def initializePage(self): self.clean_errors() self.clean_wizard_errors() @@ -272,16 +464,3 @@ class ValidationPage(QtGui.QWizardPage): self.clearTable() self.resizeTable() self.timer.singleShot(0, self.do_checks) - - def do_checks(self): - """ - launches a thread to do the checks - """ - signal = self.stepChanged - self.checks = FunThread( - self._do_checks(update_signal=signal)) - self.checks.finished.connect(self._do_validation) - self.checks.begin() - #logger.debug('check thread started!') - #logger.debug('waiting for it to terminate...') - self.checks.wait() |