From c387a52f841e8933ed7282d198ed1ece863979fc Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 6 Nov 2012 01:26:05 +0900 Subject: new validation pages in a reusable MVC style using progress indicators inside QTableWidget --- src/leap/gui/progress.py | 261 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/leap/gui/progress.py (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py new file mode 100644 index 00000000..d04e0f1f --- /dev/null +++ b/src/leap/gui/progress.py @@ -0,0 +1,261 @@ +""" +classes used in progress pages +from first run wizard +""" +try: + from collections import OrderedDict +except ImportError: + # We must be in 2.6 + from leap.util.dicts import OrderedDict +import time + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from leap.baseapp.mainwindow import FunThread + +from leap.gui import mainwindow_rc + + +class ImgWidget(QtGui.QWidget): + + # XXX move to widgets + + def __init__(self, parent=None, img=None): + super(ImgWidget, self).__init__(parent) + self.pic = QtGui.QPixmap(img) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + painter.drawPixmap(0, 0, self.pic) + + +class ProgressStep(object): + """ + Data model for sequential steps + to be used in a progress page in + connection wizard + """ + NAME = 0 + DONE = 1 + + def __init__(self, stepname, done, index=None): + """ + @param step: the name of the step + @type step: str + @param done: whether is completed or not + @type done: bool + """ + self.index = int(index) if index else 0 + self.name = unicode(stepname) + self.done = bool(done) + + @classmethod + def columns(self): + return ('name', 'done') + + +class ProgressStepContainer(object): + """ + a container for ProgressSteps objects + access data in the internal dict + """ + + def __init__(self): + self.dirty = False + self.steps = {} + + def step(self, identity): + return self.step.get(identity) + + def addStep(self, step): + self.steps[step.index] = step + + def removeStep(self, step): + del self.steps[step.index] + del step + self.dirty = True + + def removeAllSteps(self): + for item in iter(self): + self.removeStep(item) + + @property + def columns(self): + return ProgressStep.columns() + + def __len__(self): + return len(self.steps) + + def __iter__(self): + for step in self.steps.values(): + yield step + + +class StepsTableWidget(QtGui.QTableWidget): + """ + initializes a TableWidget + suitable for our display purposes, like removing + header info and grid display + """ + + def __init__(self, parent=None): + super(StepsTableWidget, self).__init__(parent) + + # remove headers and all edit/select behavior + self.horizontalHeader().hide() + self.verticalHeader().hide() + self.setEditTriggers( + QtGui.QAbstractItemView.NoEditTriggers) + self.setSelectionMode( + QtGui.QAbstractItemView.NoSelection) + width = self.width() + # WTF? Here init width is 100... + # but on populating is 456... :( + + # XXX do we need this initial? + print 'init table. width=%s' % width + self.horizontalHeader().resizeSection(0, width * 0.7) + + # this disables the table grid. + # we should add alignment to the ImgWidget (it's top-left now) + self.setShowGrid(False) + + # XXX change image for done to rc + + # Note about the "done" status painting: + # + # XXX currently we are setting the CellWidget + # for the whole table on a per-row basis + # (on add_status_line method on ValidationPage). + # However, a more generic solution might be + # to implement a custom Delegate that overwrites + # the paint method (so it paints a checked tickmark if + # done is True and some other thing if checking or false). + # What we have now is quick and works because + # I'm supposing that on first fail we will + # go back to previous wizard page to signal the failure. + # A more generic solution could be used for + # some failing tests if they are not critical. + + +class ValidationPage(QtGui.QWizardPage): + """ + class to be used as an intermediate + between two pages in a wizard. + shows feedback to the user and goes back if errors, + goes forward if ok. + initializePage triggers a one shot timer + that calls do_checks. + Derived classes should implement + _do_checks and + _do_validation + """ + + # signals + + stepChanged = QtCore.pyqtSignal([str]) + + def __init__(self, parent=None): + super(ValidationPage, self).__init__(parent) + + self.steps = ProgressStepContainer() + self.progress = QtGui.QProgressBar() + + # steps table widget + self.stepsTableWidget = StepsTableWidget(self) + + layout = QtGui.QVBoxLayout() + layout.addWidget(self.progress) + layout.addWidget(self.stepsTableWidget) + + self.setLayout(layout) + self.layout = layout + + self.timer = QtCore.QTimer() + + # connect the new step status + # signal to status handler + self.stepChanged.connect( + self.onStepStatusChanged) + + self.errors = OrderedDict() + + def populateStepsTable(self): + # from examples, + # but I guess it's not needed to re-populate + # the whole table. + table = self.stepsTableWidget + table.setRowCount(len(self.steps)) + columns = self.steps.columns + table.setColumnCount(len(columns)) + + for row, step in enumerate(self.steps): + item = QtGui.QTableWidgetItem(step.name) + item.setData(QtCore.Qt.UserRole, + long(id(step))) + table.setItem(row, columns.index('name'), item) + table.setItem(row, columns.index('done'), + QtGui.QTableWidgetItem(step.done)) + self.resizeTable() + self.update() + + def clearTable(self): + # ??? -- not sure what's the difference + #self.stepsTableWidget.clear() + self.stepsTableWidget.clearContents() + + def resizeTable(self): + # resize first column to ~80% + table = self.stepsTableWidget + FIRST_COLUMN_PERCENT = 0.75 + width = table.width() + print 'populate table. width=%s' % width + table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) + + def onStepStatusChanged(self, status): + if status != "end_sentinel": + self.add_status_line(status) + + def add_status_line(self, message): + print 'adding status line...' + index = len(self.steps) + step = ProgressStep(message, False, index=index) + self.steps.addStep(step) + self.populateStepsTable() + table = self.stepsTableWidget + + # setting cell widget. + # see note on StepsTableWidget about plans to + # change this for a better solution. + + table.setCellWidget( + index - 1, + ProgressStep.DONE, + # XXX pass image in rc + ImgWidget(img=":/images/checked.png")) + + def go_back(self): + self.wizard().back() + + def go_next(self): + self.wizard().next() + + def initializePage(self): + self.steps.removeAllSteps() + self.clearTable() + self.resizeTable() + self.timer.singleShot(0, self.do_checks) + + def do_checks(self): + """ + launches a thread to do the checks + """ + signal = self.stepChanged + self.checks = FunThread( + self._do_checks(signal=signal)) + self.checks.finished.connect(self._do_validation) + self.checks.begin() + print 'check thread started!' + print 'waiting for it to terminate...' + self.checks.wait() -- cgit v1.2.3 From 6032f776cabcd04aa7f4e1f55a34ecfec2775e85 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 7 Nov 2012 04:48:12 +0900 Subject: changes to update_signal + Fix update + Rename + Changed signature (we update progress bar from slot now) --- src/leap/gui/progress.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index d04e0f1f..16a55e3a 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -7,7 +7,7 @@ try: except ImportError: # We must be in 2.6 from leap.util.dicts import OrderedDict -import time +#import time from PyQt4 import QtCore from PyQt4 import QtGui @@ -16,6 +16,8 @@ from leap.baseapp.mainwindow import FunThread from leap.gui import mainwindow_rc +CHECKMARK_IMG = ":/images/checked.png" + class ImgWidget(QtGui.QWidget): @@ -154,13 +156,13 @@ class ValidationPage(QtGui.QWizardPage): # signals - stepChanged = QtCore.pyqtSignal([str]) + stepChanged = QtCore.pyqtSignal([str, int]) def __init__(self, parent=None): super(ValidationPage, self).__init__(parent) self.steps = ProgressStepContainer() - self.progress = QtGui.QProgressBar() + self.progress = QtGui.QProgressBar(self) # steps table widget self.stepsTableWidget = StepsTableWidget(self) @@ -213,9 +215,12 @@ class ValidationPage(QtGui.QWizardPage): print 'populate table. width=%s' % width table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) - def onStepStatusChanged(self, status): - if status != "end_sentinel": + 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): print 'adding status line...' @@ -232,8 +237,8 @@ class ValidationPage(QtGui.QWizardPage): table.setCellWidget( index - 1, ProgressStep.DONE, - # XXX pass image in rc - ImgWidget(img=":/images/checked.png")) + ImgWidget(img=CHECKMARK_IMG)) + table.update() def go_back(self): self.wizard().back() @@ -253,7 +258,7 @@ class ValidationPage(QtGui.QWizardPage): """ signal = self.stepChanged self.checks = FunThread( - self._do_checks(signal=signal)) + self._do_checks(update_signal=signal)) self.checks.finished.connect(self._do_validation) self.checks.begin() print 'check thread started!' -- cgit v1.2.3 From 63fc5b19e39072f4bda26f2649e83832d68b5000 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 7 Nov 2012 06:06:40 +0900 Subject: fix issue with refresh timing using end_sentinel as a workaround for the lack of refresh. doing a pause so user gets some feedback about what's going on. error messages on select provider page should be all covered now. --- src/leap/gui/progress.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 16a55e3a..d6551939 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -7,7 +7,8 @@ try: except ImportError: # We must be in 2.6 from leap.util.dicts import OrderedDict -#import time + +import logging from PyQt4 import QtCore from PyQt4 import QtGui @@ -18,6 +19,8 @@ from leap.gui import mainwindow_rc CHECKMARK_IMG = ":/images/checked.png" +logger = logging.getLogger(__name__) + class ImgWidget(QtGui.QWidget): @@ -116,7 +119,7 @@ class StepsTableWidget(QtGui.QTableWidget): # but on populating is 456... :( # XXX do we need this initial? - print 'init table. width=%s' % width + logger.debug('init table. width=%s' % width) self.horizontalHeader().resizeSection(0, width * 0.7) # this disables the table grid. @@ -212,7 +215,7 @@ class ValidationPage(QtGui.QWizardPage): table = self.stepsTableWidget FIRST_COLUMN_PERCENT = 0.75 width = table.width() - print 'populate table. width=%s' % width + logger.debug('populate table. width=%s' % width) table.horizontalHeader().resizeSection(0, width * FIRST_COLUMN_PERCENT) def onStepStatusChanged(self, status, progress=None): @@ -223,7 +226,6 @@ class ValidationPage(QtGui.QWizardPage): self.progress.update() def add_status_line(self, message): - print 'adding status line...' index = len(self.steps) step = ProgressStep(message, False, index=index) self.steps.addStep(step) @@ -261,6 +263,6 @@ class ValidationPage(QtGui.QWizardPage): self._do_checks(update_signal=signal)) self.checks.finished.connect(self._do_validation) self.checks.begin() - print 'check thread started!' - print 'waiting for it to terminate...' + #logger.debug('check thread started!') + #logger.debug('waiting for it to terminate...') self.checks.wait() -- cgit v1.2.3 From 8118056a244ca74d16380ad26a70e3da40e7e401 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 9 Nov 2012 11:21:40 +0900 Subject: connect page merged into regvalidation. Flow nearly working with fake provider, except for authentication. --- src/leap/gui/progress.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index d6551939..b2e34e22 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -186,6 +186,12 @@ class ValidationPage(QtGui.QWizardPage): self.errors = OrderedDict() + def set_error(self, name, error): + self.errors[name] = error + + def pop_first_error(self): + return list(reversed(self.errors.items())).pop() + def populateStepsTable(self): # from examples, # but I guess it's not needed to re-populate -- cgit v1.2.3 From fe16e62fa83c24d2ef379a3f9e102d336e527656 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 12:25:11 +0900 Subject: cleanup errors on page init and on field change --- src/leap/gui/progress.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index b2e34e22..2b1b40d9 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -192,6 +192,17 @@ class ValidationPage(QtGui.QWizardPage): def pop_first_error(self): return list(reversed(self.errors.items())).pop() + def clean_errors(self): + self.errors = OrderedDict() + + def clean_wizard_errors(self, pagename=None): + if pagename is None: + pagename = getattr(self, 'prev_page', None) + if pagename is None: + return + logger.debug('cleaning wizard errors for %s' % pagename) + self.wizard().set_validation_error(pagename, None) + def populateStepsTable(self): # from examples, # but I guess it's not needed to re-populate @@ -255,6 +266,8 @@ class ValidationPage(QtGui.QWizardPage): self.wizard().next() def initializePage(self): + self.clean_errors() + self.clean_wizard_errors() self.steps.removeAllSteps() self.clearTable() self.resizeTable() -- cgit v1.2.3 From 57bd393492fde434a1e3af60b607e8e9d757b9b3 Mon Sep 17 00:00:00 2001 From: kali Date: Mon, 12 Nov 2012 21:48:22 +0900 Subject: moved thread code to gui/threads --- src/leap/gui/progress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/gui/progress.py') diff --git a/src/leap/gui/progress.py b/src/leap/gui/progress.py index 2b1b40d9..6e8abc1f 100644 --- a/src/leap/gui/progress.py +++ b/src/leap/gui/progress.py @@ -13,7 +13,7 @@ import logging from PyQt4 import QtCore from PyQt4 import QtGui -from leap.baseapp.mainwindow import FunThread +from leap.gui.threads import FunThread from leap.gui import mainwindow_rc -- cgit v1.2.3