示例#1
0
 def _reportProblem(self):
     ID, meas, cur = self._getIDmeasCur()
     self._contact = Contact(self.gui)
     self._contact.subject.setText('%s\\%s\\%s' %
                                   (ID.text(0), meas.text(0), cur.text(0)))
     self._contact.editor.text.setText(
         'E.g. bad image correction, \n   remaining vignetting, \n   image looks distorted'
     )
     self._contact.show()
示例#2
0
class TabCheck(QtWidgets.QSplitter):
    help = '''Although applied image processing routines automatically detect
PV modules and cells, manual verification / modification can be needed
in case device corners and grid parameters were detected wrongly or the camera correction
causes erroneous results.
XXXXXXXXXXXXXXX
    '''

    def __init__(self, gui=None):
        super().__init__()

        self.gui = gui
        self.vertices_list = []
        self._lastP = None

        self._grid = CompareGridEditor()
        self._grid.gridChanged.connect(self._updateGrid)
        #         self._grid.cornerChanged.connect(self._updateCorner)
        self._grid.verticesChanged.connect(self._updateVertices)

        self.btn_markCurrentDone = QtWidgets.QPushButton("Mark verified")
        self.btn_markCurrentDone.clicked.connect(self._toggleVerified)

        self._grid.bottomLayout.addWidget(self.btn_markCurrentDone, 0, 0)

        self.list = QTreeWidget()
        self.list.setHeaderHidden(True)

        btnReset = QtWidgets.QPushButton('Reset')
        btnReset.clicked.connect(self._resetAll)

        btnSubmit = QtWidgets.QPushButton('Submit')
        btnSubmit.clicked.connect(self._acceptAll)

        llist = QtWidgets.QHBoxLayout()
        llist.addWidget(QtWidgets.QLabel("All:"))
        llist.addWidget(btnReset)
        llist.addWidget(btnSubmit)
        llist.addStretch()

        l3 = QtWidgets.QVBoxLayout()
        l3.addLayout(llist)
        l3.addWidget(self.list)

        btn_actions = QtWidgets.QPushButton("Actions")
        menu = QtWidgets.QMenu()

        menu.addAction("Reset changes").triggered.connect(self._resetChanges)
        a = menu.addAction("Recalculate all measurements")
        a.setToolTip(
            '''Choose this option to run image processing on all submitted images
of the selected module again. This is useful, since QELA image processing routines are continuously developed
and higher quality results can be available. Additionally, this option will define a new 
template image (the image other images are perspectively aligned to) depending on the highest resolution/quality  
image within the image set.''')
        a.triggered.connect(self._processAllAgain)

        menu.addAction("Report a problem").triggered.connect(
            self._reportProblem)
        menu.addAction("Upload images again").triggered.connect(
            self._uploadAgain)
        menu.addAction("Remove measurement").triggered.connect(
            self._removeMeasurement)

        btn_actions.setMenu(menu)
        l3.addWidget(btn_actions)

        wleft = QtWidgets.QWidget()
        wleft.setLayout(l3)
        self.addWidget(wleft)
        self.addWidget(self._grid)

        self.list.currentItemChanged.connect(self._loadImg)

        if self.gui is not None:
            self._timer = QtCore.QTimer()
            self._timer.setInterval(3000)
            self._timer.timeout.connect(self.checkUpdates)
            self._timer.start()

    def _processAllAgain(self):
        # TODO
        reply = QtWidgets.QMessageBox.question(
            self, 'TOD:', "This option is not available at the moment, SORRY",
            QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)

    def _uploadAgain(self):
        items = self.list.getAffectedItems()
        lines = []
        for item in items:
            data = item.data(1, QtCore.Qt.UserRole)

            agenda = self.gui.PATH_USER.join('upload',
                                             data['timestamp'] + '.csv')
            lines.extend(agendaFromChanged(agenda, data))

        self.gui.tabUpload.dropLines(lines)

    def _getAffectedPaths(self):
        items = self.list.getAffectedItems()
        out = []
        for item in items:
            p = item.parent()
            pp = p.parent()
            out.append("%s\\%s\\%s" % (pp.text(0), p.text(0), item.text(0)))
        return out

    def _removeMeasurement(self):
        affected = self._getAffectedPaths()
        if affected:
            box = QtWidgets.QMessageBox()
            box.setStandardButtons(box.Ok | box.Cancel)
            box.setWindowTitle('Remove measurement')
            box.setText("Do you want to remove ...\n%s" % "\n".join(affected))
            box.exec_()

            if box.result() == box.Ok:
                self.gui.server.removeMeasurements(*affected)
                self.checkUpdates()

    def _reportProblem(self):
        ID, meas, cur = self._getIDmeasCur()
        self._contact = Contact(self.gui)
        self._contact.subject.setText('%s\\%s\\%s' %
                                      (ID.text(0), meas.text(0), cur.text(0)))
        self._contact.editor.text.setText(
            'E.g. bad image correction, \n   remaining vignetting, \n   image looks distorted'
        )
        self._contact.show()

    def _resetAll(self):
        reply = QtWidgets.QMessageBox.question(self, 'Resetting all changes',
                                               "Are you sure?",
                                               QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.Yes:
            self._resetChanges(self.list.invisibleRootItem())

    def _resetChangesCurrentItem(self):
        item = self.list.currentItem()
        return self._resetChanges(item)

    def _excludeUnchangableKeys(self, data):
        if 'vertices' in data:
            return {k: data[k] for k in ['verified', 'vertices']}
        return data

    def _resetChanges(self, item):
        def _reset(item):
            data = item.data(1, QtCore.Qt.UserRole)
            if data is not None:
                item.setData(0, QtCore.Qt.UserRole,
                             self._excludeUnchangableKeys(data))

            f = item.font(0)
            f.setBold(False)
            item.setFont(0, f)

            for i in range(item.childCount()):
                _reset(item.child(i))

        _reset(item)

        cur = self._getIDmeasCur()[2]
        data = cur.data(0, QtCore.Qt.UserRole)
        self._grid.grid.setVertices(data['vertices'])

        self._changeVerifiedColor(item)

    def _isIDmodified(self, ID):
        data0 = ID.data(0, QtCore.Qt.UserRole)
        data1 = ID.data(1, QtCore.Qt.UserRole)
        return (data0['nsublines'] != data1['nsublines']
                or data0['grid'] != data1['grid'])

    def _updateVertices(self, vertices):
        cur = self._getIDmeasCur()[2]

        data = cur.data(1, QtCore.Qt.UserRole)
        originalvertices = data['vertices']
        data['vertices'] = vertices
        cur.setData(0, QtCore.Qt.UserRole, data)

        changed = not np.allclose(
            vertices, np.array(originalvertices), rtol=0.01)

        f = cur.font(0)
        f.setBold(changed)
        cur.setFont(0, f)

    def _updateGrid(self, key, val):
        ID = self._getIDmeasCur()[0]
        data = ID.data(0, QtCore.Qt.UserRole)
        data[key] = val
        ID.setData(0, QtCore.Qt.UserRole, data)
        f = ID.font(0)
        changed = self._isIDmodified(ID)
        f.setBold(changed)
        ID.setFont(0, f)

    def _getIDmeasCur(self):
        '''
        returns current items for ID, measurement and current
        '''
        item = self.list.currentItem()
        p = item.parent()
        if p is not None:
            pp = p.parent()
            if pp is not None:
                ID, meas, cur = pp, p, item
            else:
                ID, meas, cur = p, item, item.child(0)
        else:
            ID, meas, cur = (item, item.child(0), item.child(0).child(0))
        return ID, meas, cur

    def _loadImg(self):
        try:
            ID, meas, cur = self._getIDmeasCur()
            txt = ID.text(0), meas.text(0), cur.text(0)
            root = self.gui.projectFolder()
            p = root.join(*txt)
            if p == self._lastP:
                return
            self._lastP = p

            p0 = p.join("prev_A.jpg")
            p1 = p.join("prev_B.jpg")
            ll = len(root) + 1
            if not p0.exists():
                self.gui.server.download(p0[ll:], root)
            if not p1.exists():
                self.gui.server.download(p1[ll:], root)

            img = imread(p0)
            self._grid.imageview.setImage(img, autoRange=False)

            img = imread(p1)
            self._grid.imageview2.setImage(img, autoRange=False)

            # load/change grid
            cells = ID.data(0, QtCore.Qt.UserRole)['grid']
            nsublines = ID.data(0, QtCore.Qt.UserRole)['nsublines']

            cdata = cur.data(0, QtCore.Qt.UserRole)
            #             print(1111123, self._grid.grid.vertices())
            #             print(cdata['vertices'])
            vertices = cdata['vertices']

            # TODO: remove different conventions
            #             cells = cells[::-1]
            #             nsublines = nsublines[::-1]

            vertices = np.array(vertices)[np.array([0, 3, 2, 1])]
            #             print(vertices, 9898)

            self._grid.grid.setNCells(cells)
            self._grid.grid.setVertices(vertices)
            #             print(self._grid.grid.vertices(), 888888888888888888)

            self._grid.edX.setValue(cells[0])
            self._grid.edY.setValue(cells[1])
            self._grid.edBBX.setValue(nsublines[0])
            self._grid.edBBY.setValue(nsublines[1])
            self._updateBtnVerified(cdata['verified'])
        except AttributeError as e:
            print(e)

    def toggleShowTab(self, show):
        t = self.gui.tabs
        t.setTabEnabled(t.indexOf(self), show)

    def buildTree(self, tree):
        show = bool(tree)
        self.toggleShowTab(show)
        if show:
            root = self.list.invisibleRootItem()
            last = [root, None, None]

            def _addParam(name, params, nindents):
                if nindents:
                    parent = last[nindents - 1]
                else:
                    parent = root
                item = self.list.findChildItem(parent, name)
                #                 if params:
                #                     params = json.loads(params)
                if item is None:
                    item = QtWidgets.QTreeWidgetItem(parent, [name])
                    if params:
                        if nindents == 2:
                            # modifiable:
                            item.setData(0, QtCore.Qt.UserRole,
                                         self._excludeUnchangableKeys(params))
                        else:
                            # nindents==1 -> grid
                            item.setData(0, QtCore.Qt.UserRole, params)
                        self._changeVerifiedColor(item)
                last[nindents] = item
                if params:
                    params = params
                    # original:
                    item.setData(1, QtCore.Qt.UserRole, params)

            # add new items / update existing:
            treenames = []
            for ID, param, meas in tree:
                _addParam(ID, param, 0)
                treenames.append([ID])
                for m, currents in meas:
                    _addParam(m, None, 1)
                    treenames.append([ID, m])
                    for c, param in currents:
                        _addParam(c, param, 2)
                        treenames.append([ID, m, c])
            # remove items that are in client but not in server tree:
            for item, texts in self.list.recursiveItemsText():
                if texts not in treenames:
                    p = item.parent()
                    if not p:
                        p = root
                    p.takeChild(p.indexOfChild(item))
            root.sortChildren(0, QtCore.Qt.AscendingOrder)

            self.list.resizeColumnToContents(0)
            self.list.setCurrentItem(self.list.itemAt(0, 0))

    def modules(self):
        '''
        return generator for all module names in .list
        '''
        item = self.list.invisibleRootItem()
        for i in range(item.childCount()):
            yield item.child(i).text(0)

    def checkUpdates(self):
        if self.gui.server.isReady() and self.gui.server.hasNewCheckTree():
            self.buildTree(self.gui.server.checkTree())

    def _toggleVerified(self):
        item = self._getIDmeasCur()[2]
        data = item.data(0, QtCore.Qt.UserRole)
        v = data['verified'] = not data['verified']
        item.setData(0, QtCore.Qt.UserRole, data)

        self._updateBtnVerified(v)
        self._changeVerifiedColor(item)

    def _updateBtnVerified(self, verified):
        if verified:
            self.btn_markCurrentDone.setText('Mark unverified')
        else:
            self.btn_markCurrentDone.setText('Mark verified    ')

    def _changeVerifiedColor(self, item):
        data = item.data(0, QtCore.Qt.UserRole)
        if data is None or 'verified' not in data:
            return
        if data['verified']:
            color = QtCore.Qt.darkGreen
        else:
            color = QtCore.Qt.black
        item.setForeground(0, color)

        # apply upwards, if there is only one item in list
        while True:
            parent = item.parent()
            if not parent:
                break
            if parent.childCount() == 1:
                item = parent
                item.setForeground(0, color)

    def _acceptAll(self):
        reply = QtWidgets.QMessageBox.question(self, 'Submitting changes',
                                               "Are you sure?",
                                               QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.Yes:
            self._doSubmitAllChanges()

    def _doSubmitAllChanges(self):
        out = {'grid': {}, 'unchanged': {}, 'changed': {}}
        for item, nindent in self.list.buildCheckTree():
            data = item.data(0, QtCore.Qt.UserRole)
            if nindent == 1:
                # only grid and curr interesting
                continue
            path = '\\'.join(self.list.itemInheranceText(item))
            changed = item.font(0).bold()
            print(path, data, nindent)
            if nindent == 2:
                if changed:  # item is modified
                    out['changed'][path] = data
                else:
                    out['unchanged'][path] = data['verified']
            elif changed:
                out['grid'][path] = data  # ['verified']
        self.gui.server.submitChanges(json.dumps(out) + '<EOF>')

    def config(self):
        return {}  # 'name': self.camOpts.currentText()}

    def restore(self, c):
        pass
示例#3
0
class MainWindow(QtWidgets.QMainWindow):
    PATH = client.PATH
    sigMoved = QtCore.pyqtSignal(QtCore.QPoint)

    def __init__(self, server, user, pwd):
        super().__init__()
        self.user = user
        self.pwd = pwd
        self.server = server

        self.PATH_USER = self.PATH.mkdir(user)
        # TODO: read last root from config
        self.root = self.PATH_USER.mkdir("local")
        self.updateProjectFolder()

        self._lastW = None
        self._about, self._api, self._contact, \
            self.help, self._pricing, self._security = None, None, None, None, None, None

        self.setWindowIcon(QtGui.QIcon(client.ICON))
        self.updateWindowTitle()

        self.resize(1100, 600)

        ll = QtWidgets.QHBoxLayout()
        w = QtWidgets.QWidget()
        w.setLayout(ll)
        self.setCentralWidget(w)

        self.progressbar = CancelProgressBar()
        self.progressbar.hide()

        self.setStatusBar(StatusBar())
        self.statusBar().addPermanentWidget(self.progressbar, stretch=1)

        self.server.sigError.connect(self.statusBar().showError)

        #         self.btnMenu = QtWidgets.QPushButton('Menu')
        self.btnMenu = QtWidgets.QPushButton()
        self.btnMenu.setIcon(
            QtGui.QIcon(client.MEDIA_PATH.join('btn_menu.svg')))

        # make button smaller:
        self.btnMenu.setIconSize(QtCore.QSize(20, 20))
        #         self.btnMenu.sizeHint = lambda: QtCore.QSize(40, 20)

        self.btnMenu.setFlat(True)
        self._menu = QtWidgets.QMenu()
        a = self._menu.addAction('About QELA')
        a.setToolTip(About.__doc__)
        a.triggered.connect(self._menuShowAbout)
        a = self._menu.addAction('Help')
        a.setToolTip(Help.__doc__)
        a.triggered.connect(self._menuShowHelp)

        a = self._menu.addAction('Change current project')
        a.setToolTip(Projects.__doc__)
        a.triggered.connect(self._menuShowProjects)

        a = self._menu.addAction('Pricing')
        a.setToolTip(Pricing.__doc__)
        a.triggered.connect(self._menuShowPlan)

        a = self._menu.addAction('Website')
        a.setToolTip('Open the application website in your browser')
        a.triggered.connect(self._menuShowWebsite)

        a = self._menu.addAction('Contact')
        a.setToolTip(Contact.__doc__)
        a.triggered.connect(self.menuShowContact)

        self.btnMenu.clicked.connect(self._menuPopup)
        self.btnMenu.setSizePolicy(QtWidgets.QSizePolicy.Minimum,
                                   QtWidgets.QSizePolicy.Minimum)

        self.tabs = QtWidgets.QTabWidget()
        self.tabs.setCornerWidget(self.btnMenu)

        self.help = Help(self)
        self.help.hide()

        self.tabUpload = TabUpload(self)
        self.tabDownload = TabDownload(self)
        self.tabCheck = TabCheck(self)
        self.config = TabConfig(self)
        self.loadConfig()

        self.tabs.addTab(self.config, "Configuration")
        self.tabs.addTab(self.tabUpload, "Upload")
        self.tabs.addTab(self.tabCheck, "Check")
        self.tabs.addTab(self.tabDownload, "Download")

        self.tabCheck.checkUpdates()

        self.tabs.currentChanged.connect(self._activateTab)
        self.tabs.currentChanged.connect(self.help.tabChanged)

        self.tabs.setCurrentIndex(1)

        ll.setContentsMargins(0, 0, 0, 0)

        ll.addWidget(self.tabs)
        ll.addWidget(self.help)

        self._tempBars = []

    def updateProjectFolder(self):

        self._proj = self.server.projectCode()
        self.root.mkdir(self._proj)

    def projectFolder(self):
        return self.root.join(self._proj)

    def _menuShowProjects(self):
        self._projects = Projects(self)
        self._projects.show()

    def loadConfig(self):
        try:
            c = self.server.lastConfig()
            self.config.restore(c)
        except json.decoder.JSONDecodeError:
            print('ERROR loading last config: %s' % c)

    def updateWindowTitle(self, project=None):
        if project is None:
            project = self.server.projectName()
        self.setWindowTitle(
            'QELA | User: %s | Project: %s | Credit: %s' %
            (self.user, project, self.server.remainingCredit()))

    def _menuShowAbout(self):
        if self._about is None:
            self._about = About(self)
        self._about.show()

    def _menuShowHelp(self):
        if self.help.isVisible():
            self.help.hide()
        else:
            self.help.show()

    def _menuShowWebsite(self):
        os.startfile('http://%s' % self.server.address[0])

    def _menuShowPlan(self):
        if self._pricing is None:
            self._pricing = Pricing(self)
        self._pricing.show()

    def menuShowContact(self):
        if self._contact is None:
            self._contact = Contact(self)
        self._contact.show()

    def modules(self):
        '''return a list of all modules either found in imageuploadtable (from client)
        or tabCheck (from server)'''
        ll = list(self.tabCheck.modules())
        ll.extend(self.tabUpload.table.modules())
        return ll

    def openImage(self, path):
        txt = self.config.analysis.cbViewer.currentText()
        if txt == 'dataArtist':
            dataArtist.importFile(path)
        elif txt == 'Inline':
            self._tempview = InlineView(path)
            self._tempview.show()
        else:
            os.startfile(path)

    def _menuPopup(self):
        g = self.btnMenu.geometry()
        p = g.bottomLeft()
        p.setX(p.x() - (self._menu.sizeHint().width() - g.width()))
        self._menu.popup(self.mapToGlobal(p))

#     def _toggleShowHelp(self, checked):
#         if not checked:
#             self.help.hide()
#             self.tabs.setCornerWidget(self.btnMenu)
#             self.btnMenu.show()
#
#         else:
#             self.tabs.setCornerWidget(None)
#             s = self.btnMenu.size()
#             self.help.ltop.addWidget(self.btnMenu, stretch=0)
#
#             self.help.show()
#             self.btnMenu.setFixedSize(s)
#             self.btnMenu.show()

#     def eventFilter(self, _obj, event):
#         if self.help.isVisible():
#             #             if event.type() == QtCore.QEvent.WindowActivate:
#             #                 print("widget window has gained focus")
#             #             elif event.type() == QtCore.QEvent.WindowDeactivate:
#             #                 print("widget window has lost focus")
#             if event.type() == QtCore.QEvent.FocusIn:
#                 print("widget window has lost focus")
#         return False  # event.accept()

    def removeTemporaryProcessBar(self, bar):
        self._tempBars.remove(bar)
        self.statusBar().removeWidget(bar)
        self.progressbar.show()

    def addTemporaryProcessBar(self):
        c = CancelProgressBar()
        self._tempBars.append(c)
        self.progressbar.hide()

        #         self.statusBar().removePermanentWidget
        self.statusBar().addPermanentWidget(c, stretch=1)
        return c

    def moveEvent(self, evt):
        self.sigMoved.emit(evt.pos())
#         return QtWidgets.QMainWindow.moveEvent(self, *args, **kwargs)

    def closeEvent(self, ev):
        if not self.server.isReady():
            msg = QtWidgets.QMessageBox()
            msg.setText("You are still uploading/downloading data")
            msg.setStandardButtons(QtWidgets.QMessageBox.Ok
                                   | QtWidgets.QMessageBox.Cancel)
            msg.exec_()
            if msg.result() == QtWidgets.QMessageBox.Ok:
                ev.accept()
            else:
                ev.ignore()
                return
        self.server.logout()
        #         self.server.close()
        #         sleep(1)
        QtWidgets.QApplication.instance().quit()

    def setTabsEnabled(self, enable=True, exclude=None):
        for i in range(self.tabs.count() - 1):
            if i != exclude:
                self.tabs.setTabEnabled(i, enable)
        if enable:
            self.tabs.setCurrentIndex(2)

    def _activateTab(self, index):
        w = self.tabs.widget(index)
        if self._lastW and hasattr(self._lastW, 'deactivate'):
            self._lastW.deactivate()
        if hasattr(w, 'activate'):
            w.activate()
        self._lastW = w
示例#4
0
 def menuShowContact(self):
     if self._contact is None:
         self._contact = Contact(self)
     self._contact.show()
示例#5
0
class TabCheck(QtWidgets.QSplitter):
    '''Nobody is perfect. Although our image processing routines are fully automated,
    they can sometimes fail to precisely detect a solar module. Especially for
    uncommon module types or low quality images your help for verify or alter our results
    can be needed. This tab displays results from camera and perspective correction
    of all EL images in your current project. In  here,  images are highly compressed
    to  reduce download times. 
    
    As soon as new results are available,  this tab will be highlighted.
    Please take your time to go through the results. You can verify of change:
        * Position of the four module corners.
        * Position of the bottom left corner
        * Number of horizontal/vertical cells and busbars.
    After clicking on <Submit changes> all images modified by you will be processed again.
    Manual verification increases quality of the generated module report.
    Please inform  us, if you find odd or erroneous results. For this click  on 
            Actions -> Report a problem
    '''
    def __init__(self, gui=None):
        super().__init__()

        self.gui = gui
        self.vertices_list = []
        self._lastP = None

        self._grid = CompareGridEditor()
        self._grid.gridChanged.connect(self._updateGrid)
        #         self._grid.cornerChanged.connect(self._updateCorner)
        self._grid.verticesChanged.connect(self._updateVertices)

        self.btn_markCurrentDone = QtWidgets.QPushButton("Mark verified")
        self.btn_markCurrentDone.setToolTip(
            '''Confirm  that detected module position and type are correct. 
As soon as all modules are verified, click on <Submit> to inform our server.'''
        )
        self.btn_markCurrentDone.clicked.connect(self._toggleVerified)

        self._grid.bottomLayout.addWidget(self.btn_markCurrentDone, 0, 0)

        self.list = QTreeWidget()
        self.list.setHeaderHidden(True)
        self.list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(
            lambda pos: self._menu.popup(pos))

        btnSubmit = QtWidgets.QPushButton('Submit')
        btnSubmit.setToolTip('''Submit all changes to  the server. 
Correct results will  be marked as 'verified' in the module report and modified result will be 
processed again.''')
        btnSubmit.clicked.connect(self._acceptAll)

        llist = QtWidgets.QHBoxLayout()
        llist.addStretch()
        llist.addWidget(btnSubmit)

        l3 = QtWidgets.QVBoxLayout()
        l3.addLayout(llist)
        l3.addWidget(self.list)

        btn_actions = QtWidgets.QPushButton("Actions")
        self._menu = menu = QMenu()
        menu.aboutToShow.connect(self._showMenu)

        m = menu.addMenu('All')
        a = m.addAction("Reset changes")
        a.setToolTip('Reset everything to the state given by the server.')
        a.triggered.connect(self._resetAll)

        a = m.addAction("Recalculate all measurements")
        a.setToolTip(
            '''Choose this option to run image processing on all submitted images
of the selected module again. This is useful, since QELA image processing routines are continuously developed
and higher quality results can be available. Additionally, this option will define a new 
template image (the image other images are perspectively aligned to) depending on the highest resolution/quality  
image within the image set.''')
        a.triggered.connect(self._processAllAgain)

        a = menu.addAction("Reset changes")
        a.setToolTip('Reset everything to the state given  by the server.')
        a.triggered.connect(self._resetChanges)

        a = menu.addAction("Report a problem")
        a.setToolTip(
            'Write us a mail and inform us on the problem you experience.')
        a.triggered.connect(self._reportProblem)

        a = menu.addAction("Upload images again")
        a.setToolTip(
            'Click this button to  upload and process the images of the selected measurement again.'
        )
        a.triggered.connect(self._uploadAgain)

        self._aRemove = a = menu.addAction("Remove measurement")
        a.setToolTip(
            'Remove the current measurement/device from the project. This includes all corresponding data. Pleas write us a mail, to undo this step.'
        )
        a.triggered.connect(self._removeMeasurement)

        btn_actions.setMenu(menu)
        l3.addWidget(btn_actions)

        wleft = QtWidgets.QWidget()
        wleft.setLayout(l3)

        self.btnCollapse = QtWidgets.QPushButton(wleft)
        self.btnCollapse.setIcon(
            QtGui.QIcon(client.MEDIA_PATH.join('btn_toggle_collapse.svg')))
        self.btnCollapse.toggled.connect(self._toggleExpandAll)
        self.btnCollapse.setCheckable(True)
        self.btnCollapse.setFlat(True)
        self.btnCollapse.resize(15, 15)
        self.btnCollapse.move(7, 15)
        header = QtWidgets.QLabel('ID > Meas > Current', wleft)
        header.move(25, 7)

        self.addWidget(wleft)
        self.addWidget(self._grid)

        self.list.currentItemChanged.connect(self._loadImg)

        if self.gui is not None:
            self._timer = QtCore.QTimer()
            self._timer.setInterval(5000)
            self._timer.timeout.connect(self.checkUpdates)
            self._timer.start()

    def saveState(self):

        try:
            ID, meas, cur, typ = self._getIDmeasCur()
        except AttributeError:  # no items
            ll = []
        else:
            if typ == 'device':
                ll = [ID.text(0)]
            elif typ == 'measurement':
                ll = [ID.text(0), meas.text(0)]
            else:
                ll = [ID.text(0), meas.text(0), cur.text(0)]

        return {'expanded': self.btnCollapse.isChecked(), 'selected': ll}

    def restoreState(self, state):
        self.btnCollapse.setChecked(state['expanded'])
        self._selectFromName(state['selected'])

    def _selectFromName(self, ll):
        if ll:
            root = self.list.invisibleRootItem()

            def fn(name, parent, ll
                   ):  # try to select item by listed name ll=[ID,meas,current]
                for i in range(parent.childCount()):
                    child = parent.child(i)
                    if child.text(0) == name:
                        if ll:
                            return fn(ll.pop(0), child, ll)
                        else:
                            self.list.setCurrentItem(child)

            fn(ll.pop(0), root, ll)

    def _toggleExpandAll(self, checked):
        if checked:
            self.list.expandAll()
        else:
            self.list.collapseAll()

    def _showMenu(self):
        self._aRemove.setText('Remove %s' % self._getIDmeasCur()[-1])

    def _processAllAgain(self):
        # TODO
        reply = QtWidgets.QMessageBox.question(
            self, 'TOD:', "This option is not available at the moment, SORRY",
            QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)

    def _uploadAgain(self):
        items = self.list.getAffectedItems()
        lines = []
        for item in items:
            data = item.data(1, QtCore.Qt.UserRole)

            agenda = self.gui.PATH_USER.join('upload',
                                             data['timestamp'] + '.csv')
            lines.extend(agendaFromChanged(agenda, data))

        self.gui.tabUpload.dropLines(lines)

    def _getAffectedPaths(self):
        items = self.list.getAffectedItems()
        out = []
        for item in items:
            p = item.parent()
            pp = p.parent()
            out.append("%s\\%s\\%s" % (pp.text(0), p.text(0), item.text(0)))
        return out

    def _removeMeasurement(self):
        affected = self._getAffectedPaths()
        if affected:
            box = QtWidgets.QMessageBox()
            box.setStandardButtons(box.Ok | box.Cancel)
            box.setWindowTitle('Remove measurement')
            box.setText("Do you want to remove ...\n%s" % "\n".join(affected))
            box.exec_()

            if box.result() == box.Ok:
                res = self.gui.server.removeMeasurements(*affected)
                if res != 'OK':
                    QtWidgets.QMessageBox.critical(
                        self, 'Error removing measurements', res)
                else:
                    item = self.list.currentItem()
                    parent = item.parent()
                    if parent is None:
                        parent = self.list.invisibleRootItem()
                    parent.removeChild(item)

#                 self.checkUpdates()

    def _reportProblem(self):
        ID, meas, cur = self._getIDmeasCur()[:-1]
        self._contact = Contact(self.gui)
        self._contact.subject.setText('%s\\%s\\%s' %
                                      (ID.text(0), meas.text(0), cur.text(0)))
        self._contact.editor.text.setText(
            'E.g. bad image correction, \n   remaining vignetting, \n   image looks distorted'
        )
        self._contact.show()

    def _resetAll(self):
        reply = QtWidgets.QMessageBox.question(self, 'Resetting all changes',
                                               "Are you sure?",
                                               QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.Yes:
            self._resetChanges(self.list.invisibleRootItem())

    def _resetChangesCurrentItem(self):
        item = self.list.currentItem()
        return self._resetChanges(item)

    def _excludeUnchangableKeys(self, data):
        if 'vertices' in data:
            return {k: data[k] for k in ['verified', 'vertices']}
        return data

    def _resetChanges(self, item):
        if type(item) is bool:
            item = self.list.currentItem()

        def _reset(item):
            data = item.data(1, QtCore.Qt.UserRole)
            if data is not None:
                item.setData(0, QtCore.Qt.UserRole,
                             self._excludeUnchangableKeys(data))

            f = item.font(0)
            f.setBold(False)
            item.setFont(0, f)

            for i in range(item.childCount()):
                _reset(item.child(i))

        _reset(item)

        cur = self._getIDmeasCur()[2]
        data = cur.data(0, QtCore.Qt.UserRole)
        self._grid.grid.setVertices(data['vertices'])

        self._changeVerifiedColor(item)

    def _isIDmodified(self, ID):
        data0 = ID.data(0, QtCore.Qt.UserRole)
        data1 = ID.data(1, QtCore.Qt.UserRole)
        return (data0['nsublines'] != data1['nsublines']
                or data0['grid'] != data1['grid'])

    def _updateVertices(self, vertices):
        cur = self._getIDmeasCur()[2]

        data = cur.data(1, QtCore.Qt.UserRole)
        originalvertices = data['vertices']
        data['vertices'] = vertices
        cur.setData(0, QtCore.Qt.UserRole, data)

        changed = not np.allclose(
            vertices, np.array(originalvertices), rtol=0.01)

        f = cur.font(0)
        f.setBold(changed)
        cur.setFont(0, f)

    def _updateGrid(self, key, val):
        ID = self._getIDmeasCur()[0]
        # update data:
        data = ID.data(0, QtCore.Qt.UserRole)
        data[key] = val
        ID.setData(0, QtCore.Qt.UserRole, data)
        # font -> bold
        f = ID.font(0)
        changed = self._isIDmodified(ID)
        f.setBold(changed)
        ID.setFont(0, f)
#         # grid is identical for all current and measurements of one device, so
#         # update other items:
#         for i in range(ID.childCount()):
#             meas = ID.child(i)
#             for j in range(meas.childCount()):
#                 current = meas.child(j)

    def _getIDmeasCur(self):
        '''
        returns ID, meas, cur, typ
        of current item index(0...id,1...meas,2...current)
        '''
        item = self.list.currentItem()
        p = item.parent()
        if p is not None:
            pp = p.parent()
            if pp is not None:  # item is current
                ID, meas, cur = pp, p, item
                index = 'current'
            else:  # item is measurement
                ID, meas, cur = p, item, item.child(0)
                index = 'measurement'
        else:  # item is ID
            ID, meas, cur = (item, item.child(0), item.child(0).child(0))
            index = 'device'
        return ID, meas, cur, index

    def _loadImg(self):
        if not self.list.currentItem():
            return
        try:
            ID, meas, cur = self._getIDmeasCur()[:-1]
            txt = ID.text(0), meas.text(0), cur.text(0)
            root = self.gui.projectFolder()
            p = root.join(*txt)
            if p == self._lastP:
                return
            self._lastP = p

            p0 = p.join(".prev_A.jpg")
            p1 = p.join(".prev_B.jpg")
            ll = len(root) + 1
            if not p0.exists():
                self.gui.server.download(p0[ll:], root.join(p0[ll:]))
            if not p1.exists():
                self.gui.server.download(p1[ll:], root.join(p1[ll:]))

            self._grid.readImg1(p0)
            self._grid.readImg2(p1)

            # load/change grid
            idata = ID.data(0, QtCore.Qt.UserRole)
            cells = idata['grid'][::-1]
            nsublines = idata['nsublines']

            cdata = cur.data(0, QtCore.Qt.UserRole)
            vertices = cdata['vertices']

            # TODO: remove different conventions
            #             vertices = np.array(vertices)[np.array([0, 3, 2, 1])]

            self._grid.grid.setNCells(cells)
            self._grid.grid.setVertices(vertices)

            self._grid.edX.setValue(cells[0])
            self._grid.edY.setValue(cells[1])
            self._grid.edBBX.setValue(nsublines[1])
            self._grid.edBBY.setValue(nsublines[0])
            self._updateBtnVerified(cdata['verified'])
        except AttributeError as e:
            print('error loading image: ', e)

    def toggleShowTab(self, show):
        t = self.gui.tabs
        t.setTabEnabled(t.indexOf(self), show)

    def buildTree(self, tree):
        show = bool(tree)
        if show:
            self.list.show()
            citem = self.list.currentItem()

            root = self.list.invisibleRootItem()
            last = [root, None, None]

            def _addParam(name, params, nindents):
                if nindents:
                    parent = last[nindents - 1]
                else:
                    parent = root
                item = self.list.findChildItem(parent, name)
                if item is None:
                    item = QtWidgets.QTreeWidgetItem(parent, [name])
                    if params:
                        if nindents == 2:
                            # modifiable:
                            item.setData(0, QtCore.Qt.UserRole,
                                         self._excludeUnchangableKeys(params))
                        else:
                            # nindents==1 -> grid
                            item.setData(0, QtCore.Qt.UserRole, params)

                        self._changeVerifiedColor(item)

                last[nindents] = item
                if params:
                    params = params
                    # original:
                    item.setData(1, QtCore.Qt.UserRole, params)

            # add new items / update existing:
            IDdict = {}
            for ID, data, meas in tree:
                _addParam(ID, data, 0)
                measdict = {}
                IDdict[ID] = measdict
                #                 treenames.append([ID])
                for m, currents in meas:
                    _addParam(m, None, 1)
                    curlist = []
                    measdict[m] = curlist
                    for current, data in currents:
                        _addParam(current, data, 2)
                        curlist.append(current)

            # remove old ones:
            def iterremove(parent, dic):
                c = parent.childCount()
                i = 0
                while i < c:
                    child = parent.child(i)
                    txt = child.text(0)
                    if isinstance(dic, dict):
                        iterremove(child, dic[txt])
                        if not child.childCount():
                            # ... or empty parent items
                            parent.removeChild(child)
                            c -= 1
                            i -= 1
                    elif txt not in dic:
                        # only remove 'current' items
                        parent.removeChild(child)
                        c -= 1
                        i -= 1
                    i += 1

            iterremove(root, IDdict)

            root.sortChildren(0, QtCore.Qt.AscendingOrder)
            self.list.resizeColumnToContents(0)
            if citem is None or citem.parent() is None:
                self.list.setCurrentItem(self.list.itemAt(0, 0))
        self.toggleShowTab(show)

    def modules(self):
        '''
        return generator for all module names in .list
        '''
        item = self.list.invisibleRootItem()
        for i in range(item.childCount()):
            yield item.child(i).text(0)

    def checkUpdates(self):
        if self.gui.server.isReady() and self.gui.server.hasNewCheckTree():
            self.buildTree(self.gui.server.checkTree())

    def _toggleVerified(self):
        item = self._getIDmeasCur()[2]
        data = item.data(0, QtCore.Qt.UserRole)
        v = data['verified'] = not data['verified']
        item.setData(0, QtCore.Qt.UserRole, data)

        self._updateBtnVerified(v)
        self._changeVerifiedColor(item)

    def _updateBtnVerified(self, verified):
        if verified:
            self.btn_markCurrentDone.setText('Mark unverified')
        else:
            self.btn_markCurrentDone.setText('Mark verified    ')

    def _changeVerifiedColor(self, item):
        data = item.data(0, QtCore.Qt.UserRole)
        if data is None or 'verified' not in data:
            return
        if data['verified']:
            color = QtCore.Qt.darkGreen
        else:
            color = QtCore.Qt.black
        item.setForeground(0, color)

        # apply upwards, if there is only one item in list
        while True:
            parent = item.parent()
            if not parent:
                break
            if parent.childCount() == 1:
                item = parent
                item.setForeground(0, color)
            else:
                break

    def _acceptAll(self):
        reply = QtWidgets.QMessageBox.question(self, 'Submitting changes',
                                               "Are you sure?",
                                               QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.Yes:
            self._doSubmitAllChanges()

    def _doSubmitAllChanges(self):
        out = {'grid': {}, 'unchanged': {}, 'changed': {}}
        for item, nindent in self.list.buildCheckTree():
            data = item.data(0, QtCore.Qt.UserRole)
            if nindent == 1:
                # only grid and curr interesting
                continue
            path = '\\'.join(self.list.itemInheranceText(item))
            changed = item.font(0).bold()
            print(path, data, nindent)
            if nindent == 2:
                if changed:  # item is modified
                    out['changed'][path] = data
                else:
                    out['unchanged'][path] = data['verified']
            elif changed:
                out['grid'][path] = data  # ['verified']
        self.gui.server.submitChanges(json.dumps(out) + '<EOF>')
示例#6
0
class MainWindow(QtWidgets.QMainWindow):
    PATH = client.PATH
    sigMoved = QtCore.pyqtSignal(QtCore.QPoint)
    sigResized = QtCore.pyqtSignal(QtCore.QSize)

    def __init__(self, login, server, user, pwd):
        super(). __init__()
        self.user = user
        self.pwd = pwd
        self.login = login
        self.server = server
        FIRST_START = not self.PATH.join(user).exists()
        self.PATH_USER = self.PATH.mkdir(user)
        # TODO: read last root from config
        self.root = self.PATH_USER.mkdir("local")
        self.updateProjectFolder()

        self._downloadQueue = []
        self._downloadThread = None
        self._tempview = None
        self._lastW = None
        self._startTourExample = False
        
        self._about, self._api, self._contact, \
            self.help, self._pricing, self._security = None, None, None, None, None, None

        self.setWindowIcon(QtGui.QIcon(client.ICON))
        self.updateWindowTitle()
        
#         QtCore.QLocale.setDefault()

        self.resize(1100, 600)

        ll = QtWidgets.QHBoxLayout()
        w = QtWidgets.QWidget()
        w.setLayout(ll)
        self.setCentralWidget(w)

        self.progressbar = CancelProgressBar()
        self.progressbar.hide()

        self.setStatusBar(StatusBar())
        self.statusBar().addPermanentWidget(self.progressbar, stretch=1)

        self.server.sigError.connect(self.statusBar().showError)

        self.btnMenu = QtWidgets.QPushButton()
        self.btnMenu.setIcon(QtGui.QIcon(
            client.MEDIA_PATH.join('btn_menu.svg')))
        # make button smaller:
        self.btnMenu.setIconSize(QtCore.QSize(20, 20))

        self.btnMenu.setFlat(True)
        self._menu = QMenu()
        
        a = self._menu.addAction('About QELA')
        a.setToolTip(About.__doc__)
        a.triggered.connect(self._menuShowAbout)
        
        a = self._menu.addAction('Help')
        f = a.font()
        f.setBold(True)
        a.setFont(f)
        a.setToolTip(Help.__doc__)
        a.triggered.connect(self._menuShowHelp)

        a = self._menu.addAction('Change current project')
        a.setToolTip(Projects.__doc__)
        a.triggered.connect(self._menuShowProjects)

        a = self._menu.addAction('Download example images')
        a.triggered.connect(self._downloadExampleImages)

        a = self._menu.addAction('Pricing')
        a.setToolTip(Pricing.__doc__)
        a.triggered.connect(self._menuShowPlan)

        a = self._menu.addAction('Website')
        a.setToolTip(
            'Open the application website in your browser')
        a.triggered.connect(self._menuShowWebsite)

        a = self._menu.addAction('Contact us')
        a.setToolTip(Contact.__doc__)
        a.triggered.connect(self.menuShowContact)

        self._menu.addSeparator()

        a = self._menu.addAction('Account')
        a.triggered.connect(self.account)

        a = self._menu.addAction('Change User')
        a.triggered.connect(self.changeUser)

        self.btnMenu.clicked.connect(self._menuPopup)
        self.btnMenu.setSizePolicy(
            QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)

        self.tabs = QtWidgets.QTabWidget()
        self.tabs.setCornerWidget(self.btnMenu)

        self.help = Help(self)
        self.help.hide()

        self.tabUpload = TabUpload(self)
        self.tabDownload = TabDownload(self)
        self.tabCheck = TabCheck(self)
        self.tabConfig = TabConfig(self)  # TODO: rename to tabconfig
#         self.loadConfig()

        self.tabs.addTab(self.tabConfig, "Configuration")
        self.tabs.addTab(self.tabUpload, "Upload")
        self.tabs.addTab(self.tabCheck, "Check")
        self.tabs.addTab(self.tabDownload, "Download")
        self.tabs.currentChanged.connect(self._activateTab)
        self.tabs.currentChanged.connect(self.help.tabChanged)

        self.tabs.setCurrentIndex(1)  # show TabUpload at start

        ll.setContentsMargins(0, 0, 0, 0)

        ll.addWidget(self.tabs)
        ll.addWidget(self.help)

        self._tempBars = []
        self.tabCheck.checkUpdates()
#         FIRST_START = True

        self.restoreLastSession()
        self.show()

        if FIRST_START:
            B = QtWidgets.QMessageBox
            b = B(B.Information,
                  'Starting QELA for the first time...',
                  '''Hi %s!
It looks like this is your first time using QELA on this PC.
Would you like to take a quick tour?''' % self.user,
                  B.Yes | B.No)
            b.setDefaultButton(B.Yes)
            b.setWindowIcon(self.windowIcon())

            if b.exec_() == B.Yes:
                self._tourInit()

    def saveSession(self):
        s = self.PATH_USER.join('session.json')
        c = {'config':self.tabConfig.saveState(),
             'upload':self.tabUpload.saveState(),
             'download':self.tabDownload.saveState(),
             'check':self.tabCheck.saveState(),
             }
        with open(s, 'w') as f:
            f.write(json.dumps(c, indent=4))

    def restoreConfigFromServer(self):
        self.tabConfig.restoreState(self.server.lastConfig())
 
    def restoreLastSession(self):
        try:
            s = self.PATH_USER.join('session.json')
            c = None
            if s.exists():
                try:
                    with open(s, 'r') as  f:
                        c = json.loads(f.read())
                except json.decoder.JSONDecodeError:
                    print('ERROR loading last session: %s' % c)
            if c is None:
                self.restoreConfigFromServer()
            else:
                self.tabConfig.restoreState(c['config'])
                self.tabDownload.restoreState(c['download'])
                self.tabUpload.restoreState(c['upload'])
                self.tabCheck.restoreState(c['check'])

        except Exception:
            print('Could not restore last session')

    def _downloadDoneExampleImages(self, path):
        if not hasattr(self, '_tourSa'):
            self._tourExample_init(path)
        else:
            # other tour is still running - start after that
            self._startTourExample = path
        
    def _tourExample_init(self, path):
        # TODO: temporarily disable all other tabs
        self._startTourExample = False
        dirname = path[:-4]  # remove zip
        with ZipFile(path) as myzip:
            myzip.extractall(dirname)
        QtGui.QDesktopServices.openUrl(
                QtCore.QUrl.fromLocalFile(dirname))
        
        self.tabs.setCurrentWidget(self.tabUpload)

        self._tourEx = Tour(self, 1, self._tourExNext,
                             'Correct example images',
                             self._tourExCleanUp)
        self._tourEx.show()
        self._tourEx.next(0)

    def _tourExCleanUp(self):
        if hasattr(self, '_dragWBtn_style'):
            # reset
            self.tabUpload.dragW.btn.setStyleSheet(self._dragWBtn_style)
            del self._dragWBtn_style

    def _tourExNext(self, tour):
        self._tourExCleanUp()
        if tour.index == -1:
    
            tour.lab.setText('''\
1. Select all folders in the open directory
2. Drag and drop them into QELA
3. Click on Button <Blocks>''')
        elif tour.index == 0:
            tour.lab.setText('''\
1. Click on Button <Blocks>
2. Click on Items <Measurement>, <Module ID> and <Current>
3. Move <Measurement>, <Module ID> and <Current> to position 3,4 and 5 respectively''')        
            self._dragWBtn_style = self.tabUpload.dragW.btn.styleSheet()
            self.tabUpload.dragW.btn.setStyleSheet('QPushButton { %s }' % tour.style)

        elif tour.index == 1:
            tour.lab.setText('''\
1. Ensure all field in the table are filled with meaningful data.
2. Go to tab 'Configuration' and make sure that 'exampleCalibration' is chosen as camera
3. Click un button upload to upload all images and start image processing.''')        

    def _downloadExampleImages(self):
        d = QtWidgets.QFileDialog.getExistingDirectory(directory=self.root)
        if d:
            self.addDownload('exampleImages.zip', PathStr(d),
                             fnsDone=self._downloadDoneExampleImages,
                             cmd='exampleImages.zip')

    def _tourInit(self):
        
        B = QtWidgets.QMessageBox
        b = B(B.Information,
              'Download example images',
              '''You can start right away using our example images.
Would you like to download them now?
You can also download them at any other time (Menu->Download example images)''',
              B.Yes | B.No)
        b.setDefaultButton(B.Yes)
        b.setWindowIcon(self.windowIcon())
        if b.exec_() == B.Yes:
            self._downloadExampleImages()
            
        self._tourSa = Tour(self, 4, self._tourNext,
                             'First Steps',
                             self._tourClose)
#         self._tourSa.show()
#         self._tourSa.next(0)

        QtCore.QTimer.singleShot(0, lambda: [self._tourSa.show(),
                                             self._tourSa.next(0)])
#                                              self._tourSa.adjustSize)

    def _tourClose(self):
        self.tabs.setStyleSheet('')
        self.btnMenu.setStyleSheet('')
        del self._tourSa

        if self._startTourExample:
            self._tourExample_init(self._startTourExample)

#             self.tabUpload.dragW.btn.setStyleSheet('')

    def _tourNext(self, tour):
        
#         self._tour_btn1.setEnabled(self._tour_index >-2)

        if tour.index == -1:  # begin of tour
            doc = About.FIRST_STEPS
            self.tabs.setStyleSheet('')

        elif tour.index == 4:  # end of tour

            self._menuPopup()
            doc = '''Click on HELP(-->) to show this help again and to 
highlight all all input fields with a tooltip'''
            self.tabs.setStyleSheet('')
            self.btnMenu.setStyleSheet('QPushButton { %s }' % tour.style)

        else:  # somewhere in the middle

            self.tabs.setCurrentIndex(tour.index)
            w = self.tabs.currentWidget()
            doc = w.__doc__
            self.btnMenu.setStyleSheet('')
            self.tabs.setStyleSheet('QTabBar::tab:selected { %s }' % tour.style)
            
#             if self.tabs.currentWidget() == self.tabUpload:
#                 # highlight block button:
#                 self._dragWBtn_style = self.tabUpload.dragW.btn.styleSheet()
#                 self.tabUpload.dragW.btn.setStyleSheet('QPushButton { %s }' % style)
#             elif hasattr(self, '_dragWBtn_style'):
#                 # reset
#                 self.tabUpload.dragW.btn.setStyleSheet(self._dragWBtn_style)
#                 del self._dragWBtn_style

        doc = dedent(doc).rstrip().lstrip()
        tour.lab.setText(doc.replace('\n', '<br>'))  # '<br><br>' + 
#         self._initTourWidgetResize()

    def updateProjectFolder(self):
        self._proj = self.server.projectCode()
        self.root.mkdir(self._proj)

    def projectFolder(self):
        return self.root.join(self._proj)

    def _menuShowProjects(self):
        self._projects = Projects(self)
        self._projects.show()

#     def loadConfig(self):
#         try:
#             c = self.server.lastConfig()
#             self.config.restore(c)
#         except json.decoder.JSONDecodeError:
#             print('ERROR loading last config: %s' % c)

    def updateWindowTitle(self, project=None):
        if project is None:
            project = self.server.projectName()
        self.setWindowTitle('QELA | User: %s | Project: %s | Credit: %s' % (
            self.user, project, self.server.remainingCredit()))

    def _menuShowAbout(self):
        if self._about is None:
            self._about = About(self)
        self._about.show()

    def _menuShowHelp(self):
        if self.help.isVisible():
            self.help.hide()
        else:
            self.help.show()

    def _menuShowWebsite(self):
        os.startfile('https://%s' % self.server.address[0])

    def _menuShowPlan(self):
        if self._pricing is None:
            self._pricing = Pricing(self)
        self._pricing.show()

    def menuShowContact(self):
        if self._contact is None:
            self._contact = Contact(self)
        self._contact.show()

    def modules(self):
        '''return a list of all modules either found in imageuploadtable (from client)
        or tabCheck (from server)'''
        ll = list(self.tabCheck.modules())
        ll.extend(self.tabUpload.table.modules())
        return set(ll)

    def verifyFile(self, path, warning=True):
        '''
        returns True if local file could be verified
        '''
        localpath = self.projectFolder().join(path)
        checksum = fileCheckSum(localpath)
        verified = self.server.verifyFile(path, checksum)
        if not warning:
            return verified
        if not verified:
            ret = QtWidgets.QMessageBox.warning(self, 'Verification error', '''File <{}> 
could  not be verified by our server. 
It is possible, that is has been tampered with. 
Would you like to  download it again?.

'''.format(path), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
            return ret == QtWidgets.QMessageBox.Yes
        return True

    def openImage(self, path, **kwargs):
        txt = self.tabConfig.preferences.cbViewer.currentText()
        if txt == 'dataArtist':
            dataArtist.importFile(path)
        elif txt == 'Inline':
            if self._tempview is None:
                self._tempview = InlineView()
            self._tempview(path, **kwargs)
        else:
            os.startfile(path)

    def _menuPopup(self):
        g = self.btnMenu.geometry()
        p = g.bottomLeft()
        p.setX(p.x() - (self._menu.sizeHint().width() - g.width()))
        self._menu.popup(self.mapToGlobal(p))

    def removeTemporaryProcessBar(self, bar):
        self._tempBars.remove(bar)
        self.statusBar().removeWidget(bar)
#         self.progressbar.show()

    def addTemporaryProcessBar(self):
        c = CancelProgressBar()
        self._tempBars.append(c)
        self.progressbar.hide()
        self.statusBar().addPermanentWidget(c, stretch=1)
        return c

    def moveEvent(self, evt):
        self.sigMoved.emit(evt.pos())
        
    def resizeEvent(self, evt):
        self.sigResized.emit(evt.size())

    def closeEvent(self, ev):
        if not self.server.isReady():
            msg = QtWidgets.QMessageBox()
            msg.setText("You are still uploading/downloading data")
            msg.setStandardButtons(
                QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
            msg.exec_()
            if msg.result() == QtWidgets.QMessageBox.Ok:
                ev.accept()
            else:
                ev.ignore()
                return
        self._close()

    def _downloadDone(self):
        self._downloadThread = None
        self.progressbar.hide()

        if len(self._downloadQueue):
            # work on next in queue
            self.addDownload(*self._downloadQueue.pop(0))

    def addDownload(self, paths, root, fnsDone=None, **kwargs):
        '''
        paths tuple/list -> multiple files, str/Pathstr -> single file
        fnsDone tuple/list -> multiple functions, else -> single function
        
        roon ... either local to-file or download folder
        '''
        b = self.progressbar
        if self._downloadThread is None:
            d = self._downloadThread = _DownloadThread(self, paths, root, **kwargs)
            if fnsDone is not None:
                if type(fnsDone) not in  (tuple, list):
                    fnsDone = [fnsDone]
                for fn in fnsDone:
                    d.sigDone.connect(fn)
            d.sigUpdate.connect(b.bar.setValue)
            d.sigDone.connect(self._downloadDone)
            d.start()

            b.setColor('darkblue')
            b.bar.setFormat("Downloading %p%")
            b.setCancel(d.kill)
            b.show()
        else:
            # already downloading something: add this one to queue
            self._downloadQueue.append((paths, root, fnsDone))

    def _close(self):
        self.saveSession()
        if self.server.isReady():
            self.hide()  # yieldOtherProgramInstances is quite slow, so close win first
            if not len(list(yieldOtherProgramInstances())):
                # is this window is the only one - no other client is opened:
                self.server.logout()

    def account(self):
        a = getattr(self, '_account', None) 
        if not a:
            self._account = a = Account(self)
        a.show()

    def changeUser(self):
        self.close()
        L = self.login.__class__(self.server, True)
        L.show()

    def setTabsEnabled(self, enable=True, exclude=None):
        for i in range(self.tabs.count() - 1):
            if i != exclude:
                self.tabs.setTabEnabled(i, enable)
        if enable:
            self.tabs.setCurrentIndex(2)

    def _activateTab(self, index):
        w = self.tabs.widget(index)
        if self._lastW and hasattr(self._lastW, 'deactivate'):
            self._lastW.deactivate()
        if hasattr(w, 'activate'):
            w.activate()
        self._lastW = w