Exemplo n.º 1
20
class VMWidget(QWidget):
        
    def __init__(self,parent = None):
        super(VMWidget,self).__init__(parent)
        
        #self.setStyleSheet("QToolTip{background-color:white;color:black;font-size:12px;}")
        #self.setFont(QFont(u"微软雅黑",12))
        self.vmInfoList = []
        self.oldVmInfoList = [] 
        self.vmOffInfoList = []
        self.threadMap = {}
        self.processMap = {}
        self.parent = parent
        self.domainManager = DomainManager()
        self.progressMonitor = ProgressThread()
        self.vmstatusTimer = QTimer()
        #self.vmstatusTimer.start(7000)
        self.connect(self.vmstatusTimer, SIGNAL("timeout()"),self.postStatusToServer)
        self.startVmInfo = None
        if common.SCROLL_TYPE != "slider":
            self.scrollArea = QScrollArea(self)
            self.scrollArea.setStyleSheet("background-color:transparent;border:0px")
            self.vmButtonList = []
            self.vmTableWidget = QTableWidget()
            self.setTableWidgetStyle()
            
            self.mainLayout = QVBoxLayout()
            self.mainLayout.setMargin(0)
            self.mainLayout.setSpacing(0)
            self.mainLayout.addWidget(self.scrollArea)
            self.mainLayout.addSpacing(10)
        
            self.setLayout(self.mainLayout)
        
        
            self.downloadingList = []
        
            
            
        if common.SCROLL_TYPE == "slider":
            #三个自定义button,用于显示相应的虚拟机
            self.firstVM   = CustomVMButton(self)
            self.secondVM  = CustomVMButton(self)
            self.thirdVM   = CustomVMButton(self)
             
            self.vmButtonList = [self.firstVM, self.secondVM, self.thirdVM]
             
            for i in range(len(self.vmButtonList)):
                self.vmButtonList[i].setIcon(QIcon("images/windows.png"))
                self.vmButtonList[i].setIconSize(QSize(100,100))
                self.vmButtonList[i].setFlat(True)
                self.vmButtonList[i].setFont(QFont("Times", 15, QFont.Bold))
            
            #设置滑动条的样式
            self.slider = QSlider(Qt.Vertical,self)
            self.slider.setStyleSheet( "QSlider::groove:vertical {  "
                                        "border: 1px solid #4A708B;  "
                                        "background: #C0C0C0;  "
                                        "width: 5px;  "
                                        "border-radius: 1px;  "
                                        "padding-left:-1px;  "
                                        "padding-right:-1px;  "
                                        "padding-top:-1px;  "
                                        "padding-bottom:-1px;  "
                                        "}"
                                        "QSlider::handle:vertical {"
                                        "height: 100px;"
                                        "background: white;"
                                        "margin: 0 -4px;"
                                        "}" )
             
            self.btnLayout = QHBoxLayout(self)
            self.btnLayout.addStretch()
            self.btnLayout.addWidget(self.firstVM)
            self.btnLayout.addWidget(self.secondVM)
            self.btnLayout.addWidget(self.thirdVM)
            self.btnLayout.addStretch()
            self.btnLayout.addSpacing(10)
            self.btnLayout.addWidget(self.slider)
            self.btnLayout.setSpacing(10)
            self.btnLayout.setMargin(10)
             
            self.setLayout(self.btnLayout)
            
    def setTableWidgetStyle(self):
        
        #self.vmTableWidget.setColumnCount(3)
        #self.vmTableWidget.setRowCount(1)
        self.vmTableWidget.setFrameShape(QFrame.NoFrame)
        self.vmTableWidget.setEditTriggers(QTableWidget.NoEditTriggers)#不能编辑
        self.vmTableWidget.setSelectionMode(QAbstractItemView.NoSelection)
        self.vmTableWidget.verticalHeader().setVisible(False)#设置垂直头不可见
        self.vmTableWidget.horizontalHeader().setVisible(False)#设置垂直头不可见
        self.vmTableWidget.setShowGrid(False)
        self.vmTableWidget.setFocusPolicy(Qt.NoFocus)
        
        self.vmTableWidget.setStyleSheet("QTableWidget{background-color: rgb(235, 235, 235,0);}")    
    
    def getVmList(self):
        vmList = []
        vmInfo = {}
        vmInfo["name"] = "15615123021654541"
        vmInfo["status"] = "offline"
        vmInfo["name"] = "offline"
        vmInfo["os_distro"] = "windows_7"
        
        
        vmInfo1 = {}
        vmInfo1["name"] = "1561545421654541"
        vmInfo1["status"] = "offline"
        vmInfo1["name"] = "offline"
        
        vmInfo2 = {}
        vmInfo2["name"] = "1561afew21654541"
        vmInfo2["status"] = "offline"
        vmInfo2["name"] = "windows_7"
        
        vmInfo3 = {}
        vmInfo3["name"] = "afewfdsafewfa"
        vmInfo3["status"] = "offline"
        vmInfo3["name"] = "windows_7"
        
        vmList.append(vmInfo)
        vmList.append(vmInfo1)
        vmList.append(vmInfo2)
        vmList.append(vmInfo3)
        vmList.append(vmInfo1)
        vmList.append(vmInfo2)
        vmList.append(vmInfo3)
        
        return vmList
    def postStatusToServer(self):
        '''serverState = VMInfoManager.instance().getCurCloudServerState()
        if serverState != "enable":
            return'''
        postInfo = {}
        state = self.domainManager.getStatus()
        if self.vmOffInfoList == [] or len(self.downloadingList) != 0:
            #self.processMap.clear()
            #self.threadMap.clear()
            return
        if len(state) == 0:
            postInfo["state"] = "shutdown"
            postInfo["vmname"] = self.vmOffInfoList[0]["vmname"]
        else:
            for item in state:
                postInfo["state"] = "running"
                if state[item].has_key("disk_io"):
                    postInfo["io_throughput"] = state[item]["disk_io"]
                if state[item].has_key("max_disk_io"):
                    postInfo["io_throughput_peak"] = state[item]["max_disk_io"]
                if state[item].has_key("net_io"):
                    postInfo["net_throughput"] = state[item]["net_io"]
                if state[item].has_key("max_net_io"):
                    postInfo["net_throughput_peak"] = state[item]["max_net_io"]
                if state[item].has_key("memory_size"):
                    postInfo["memory"] = state[item]["memory_size"]*1024
                if state[item].has_key("vmname"):
                    postInfo["vmname"] = state[item]["vmname"]
                if state[item].has_key("name"):
                    postInfo["coursename"] = state[item]["name"]
                if state[item].has_key("cpu"):
                    postInfo["cpu_utilization"] = state[item]["cpu"]     
        self.emit(SIGNAL("vmstateinfo"),postInfo)
        
    def showVmList(self):
        subWidget = QWidget()
        if self.vmstatusTimer.isActive():
            pass
        else:
            #if globalvariable.CLASS_STATUS:
            self.vmstatusTimer.start(7000)
            #self.connect(self.vmstatusTimer, SIGNAL("timeout()"),self.postStatusToServer)
        
        if self.scrollArea.widget():
            self.scrollArea.takeWidget()
            
        self.vmTableWidget = QTableWidget()
        self.setTableWidgetStyle()
        #if self.vmTableWidget
        self.vmTableWidget.clear()
    
        #self.vmInfoList = self.getVmList()
        num = len(self.vmInfoList)
        columnCount = 1
        rowCount = 1
        if num == 0:
            return
        if num <=3:
            #self.scrollArea.verticalScrollBar().hide()
            rowCount = 1
            if num == 1:
                columnCount = 1
            elif num == 2:
                columnCount = 2
            elif num == 3:
                columnCount = 3
        else:
            rowCount = num/3
            rest = num%3
            if (rowCount == 2 and rest == 0)  or rowCount == 1 :
                pass
            if rest > 0:
                rowCount+=1
                
            columnCount = 3
        
        
        parentWidth = self.parent.width()
        resolutionValue = StoreInfoParser.instance().getResolutionValue()
        if resolutionValue:
            if len(resolutionValue.split("x")) >= 2:
                parentWidth = int(resolutionValue.split("x")[0])*5/9.0
        ratio = parentWidth/800.0
        tableWidth = (parentWidth - 20*2 -50*2*ratio)/3 + 5
        if len(self.vmInfoList) > 3:
            tableWidth = (parentWidth - 20*2 -50*2*ratio)/3 - 20*(1 - ratio) + 5
                
        self.vmTableWidget.setColumnCount(columnCount)
        self.vmTableWidget.setRowCount(rowCount)
        self.vmTableWidget.setFixedWidth(parentWidth-100)
        self.vmTableWidget.setFixedHeight((tableWidth+10)*rowCount)
        self.vmTableWidget.verticalHeader().setDefaultSectionSize(tableWidth+10)
        self.vmTableWidget.horizontalHeader().setDefaultSectionSize((parentWidth-100)/columnCount)
        #self.setWidget("status")
        if columnCount <= 3 and rowCount == 1:
            for i in range(columnCount):
                self.setRowColumnWidget(0,i,self.vmInfoList[i]["name"])
        else:
            for i in range(rowCount):
                for j in range(3):
                    if i*3 + j <= num-1:
                        self.setRowColumnWidget(i, j, self.vmInfoList[i*3+j]["name"])  
        
        mainLayout = QHBoxLayout()
        mainLayout.setMargin(0)
        mainLayout.addWidget(self.vmTableWidget)
        subWidget.setLayout(mainLayout)
        #if self.scrollArea.widget():
            #self.scrollArea.takeWidget()
        self.scrollArea.setWidget(subWidget) 
        self.scrollArea.setWidgetResizable(True) 
        #self.scrollArea.horizontalScrollBar().hide()   
    def setRowColumnWidget(self,row,column,vmid):
        status = "sfw"
        #self.vmTableWidget.clear()
        mainWidget = QWidget()
        #self.vmTableWidget.hideColumn()
        #vmRowButtonList = []
        parentWidth = self.parent.width()
        resolutionValue = StoreInfoParser.instance().getResolutionValue()
        if resolutionValue:
            if len(resolutionValue.split("x")) >= 2:
                parentWidth = int(resolutionValue.split("x")[0])*5/9.0
        ratio = parentWidth/800.0
        vmWidth = (parentWidth - 20*2 -50*2*ratio)/3
        if len(self.vmInfoList) > 3:
            vmWidth = (parentWidth - 20*2 -50*2*ratio)/3 - 20*(1 - ratio)
        
        vmButton = CustomVMButton()
        vmButton.setFixedSize(QSize(vmWidth, vmWidth + 5))
        vmButton.setIconSize(QSize(vmWidth - 60*ratio,vmWidth - 30*ratio))
        vmButton.setText(self.vmInfoList[row*3 + column]["name"])
        vmButton.setFlat(True)
        vmButton.setFont(QFont("Times", 15, QFont.Bold))
        vmInfo = self.vmInfoList[row*3 + column]
        vmButton.setVMInfo(vmInfo)
        imageName = "other.png"
        if self.vmInfoList[row*3 + column].has_key("os_distro"):
            if common.imageMap.has_key(self.vmInfoList[row*3 + column]["os_distro"]):
                imageName = common.imageMap[self.vmInfoList[row*3 + column]["os_distro"]]
            else:
                LogRecord.instance().logger.info(u'common.imageMap中未找到键值%s' % self.vmInfoList[row*3 + column]["os_distro"])
                        
        vmButton.setIcon(QIcon("images/systemImage/%s" % imageName))
                
#         if globalvariable.CLASS_STATUS and not globalvariable.TICHU_STATUS:
#             self.connect(vmButton, SIGNAL("clicked()"),self.slotOpenVMLesson)
#             self.connect(vmButton, SIGNAL("controlvm"),self.slotControlVM)
#         else:
#             self.connect(vmButton, SIGNAL("clicked()"),self.slotCreateVMLesson)
                    
        #vmRowButtonList.append(vmButton)
        self.vmButtonList.append(vmButton)
        #firMybutton.setStatus("undownload")
        vmButton.setObjectName(vmid + ":" + QString.number(row) + ":" + QString.number(column))
        self.connect(vmButton, SIGNAL("clicked()"),self.startDomain)
        
            
        progressBar = WidgetProgress()
        progressBar.setFixedSize(vmWidth, vmWidth + 5)
        #progressBar.progressBar.setValue(0)
        progressBar.setVmName(self.vmInfoList[row*3 + column]["name"])
        progressBar.setObjectName(vmid + ":" + QString.number(row) + ":" + QString.number(column))
        #progressBar.objectName()
        
        #self.firMybutton.setText(self.tr("确萨"))
        
        #progressBar = QProgressBar()
        
        myLayout = QHBoxLayout()
        myLayout.setMargin(0)
        myLayout.addStretch()
        myLayout.addWidget(vmButton)
        myLayout.addWidget(progressBar)
        myLayout.addStretch()
        
        if vmid in self.downloadingList:
            vmButton.hide()
        else:
            progressBar.hide()
             
        #firMybutton.hide()
        mainWidget.setLayout(myLayout)
        
        
        self.vmTableWidget.setCellWidget(row, column, mainWidget)
        #self.mainLayout.addWidget(mainWidget)  
    
    def setVMInfoList(self, vmInfoList, vmOffInfoList):
        
        self.oldVmInfoList = LocalImgManager.instance().getCompleteList()
        self.vmInfoList = vmInfoList
        self.vmOffInfoList = vmOffInfoList
        self.domainManager.deleteImgList(vmOffInfoList)
        
#         needDeleteList = self.compareDelete(self.oldVmInfoList, vmOffInfoList)
        if self.vmOffInfoList == []:
            #self.progressMonitor.stop()
            #self.threadMap.clear()
            for item in self.processMap:
                self.processMap[item].stop()
                if self.processMap[item].isRunning():
                    self.processMap[item].exit()
            for item in self.threadMap:
                if self.threadMap[item].isRunning():
                    self.threadMap[item].exit()
                
            self.processMap.clear()
            self.threadMap.clear()
            #self.killRsyncThread()
            
#         if len(needDeleteList) == 0:
#             return
#         else:
#             self.domainManager.deleteImgList(needDeleteList)
    def stopAllDownload(self):
        for item in self.processMap:
            self.processMap[item].stop()
            if self.processMap[item].isRunning():
                self.processMap[item].exit()
                
        self.killScpThread()
        for item in self.threadMap:
            #self.threadMap[item].stop()
            if self.threadMap[item].isRunning():
                self.threadMap[item].exit()
        
        self.downloadingList = []
        self.processMap.clear()
        self.threadMap.clear()
    def killScpThread(self):
        pidList = self.getScpsProcessId()
        #sshPidList = self.getSshProcessId()
        for item in pidList:
            self.killVmId(item)
        '''for mtem in sshPidList:
            self.killVmId(mtem)'''
        
    def killVmId(self,killid):
        cmd = "kill -9 " + killid
        os.system(str(cmd))
        
    def getScpsProcessId(self):#得到运行虚拟机的进程号序列,可用于关闭某个虚拟机
        
        idList = []
        cmd = "ps aux | grep scp"
        output = ""
        statusOutput = []
        statusOutput = commands.getstatusoutput(cmd)
        if statusOutput[0] == 0:
                output = statusOutput[1]
        else:
            output = ""
        result = output.split("\n")
        for i in range(len(result)):
            if QString(result[i]).contains("scp"):
                idList.append(QString(result[i]).simplified().split(" ")[1])
    
        return idList
    
    def getSshProcessId(self):#得到运行虚拟机的进程号序列,可用于关闭某个虚拟机
        
        idList = []
        cmd = "ps aux | grep /usr/bin/ssh"
        output = ""
        statusOutput = []
        statusOutput = commands.getstatusoutput(cmd)
        if statusOutput[0] == 0:
                output = statusOutput[1]
        else:
            output = ""
        result = output.split("\n")
        for i in range(len(result)):
            if QString(result[i]).contains("/usr/bin/ssh"):
                idList.append(QString(result[i]).simplified().split(" ")[1])
    
        return idList
    
    def compareDelete(self,oldList,newList):
        nameList = []
        deleteList = []
        for item in newList:
            nameList.append(item["name"])
            
        for name in oldList:
            if name not in nameList:
                deleteList.append(name)
        return deleteList
    
    def autoStartDownload(self):
        #return
        num = len(self.vmInfoList)
        if num == 0:
            return
        else:
            #self.progressMonitor.setVmInfoList(self.vmInfoList)
            #self.progressMonitor.start()
            #self.connect(self.progressMonitor, SIGNAL("downloadover"),self.slotDownloadOver)
            imgMap = LocalImgManager.instance().getCompliteListSize()
            for i in range(len(self.vmInfoList)):
                mark = 0
                for item in imgMap:
                    if item == self.vmInfoList[i]["name"] and imgMap[item] == self.vmInfoList[i]["img_file_size"]:
                        mark = 1
                if mark == 1:
                    continue
                row = i/3
                column = i%3
                self.downloadingList.append(self.vmInfoList[i]["name"])
#                 self.downloadThread = DownloadThread()
#                 self.progressThread = ProgressThread()
                threadid = self.vmInfoList[i]["name"] + ":" + QString.number(row) + ":" + QString.number(column)
#                 self.downloadThread.setProcessId(threadid + ":" + QString.number(self.vmInfoList[i]["img_file_size"]))
#                 self.progressThread.setProcessId(threadid + ":" + QString.number(self.vmInfoList[i]["img_file_size"]))
#                 self.connect(self.progressThread, SIGNAL("currentprogress"),self.updateProgressBar)
#                 self.connect(self.progressThread, SIGNAL("downloadover"),self.slotDownloadOver)
#                 self.downloadThread.start()
#                 self.progressThread.start()
                self.setRowColumnWidget(int(row), int(column), self.vmInfoList[i]["name"])
                self.createThread(threadid,self.vmInfoList[i])
                
                #time.sleep(1)
                #return
            self.startFirstThread()
    def startFirstThread(self):
        for item in self.threadMap:
            self.threadMap[item].start()
            self.processMap[item].start()
            self.connect(self.processMap[item], SIGNAL("currentprogress"),self.updateProgressBar)
            #self.connect(self.processMap[item], SIGNAL("downloadover"),self.slotDownloadOver)
            self.connect(self.threadMap[item], SIGNAL("downloadover"),self.slotDownloadOver)
            self.connect(self.threadMap[item], SIGNAL("downloaderror"),self.slotDownloadError)
            return
        
    def slotDownloadError(self,signalid):
        pass 
    
    def createThread(self,threadid,vmInfo):
        if self.threadMap.has_key(threadid):
            if self.threadMap[threadid].isRunning():
                self.threadMap[threadid].exit()
            self.threadMap.pop(threadid)
        if self.processMap.has_key(threadid):
            if self.processMap[threadid].isRunning():
                self.processMap[threadid].exit()
            self.processMap.pop(threadid)
        
        downloadThread = DownloadThread()
        progressThread = ProgressThread()
        self.threadMap[threadid] = downloadThread
        self.processMap[threadid] = progressThread
        self.threadMap[threadid].setProcessId(threadid + ":" + str(vmInfo["img_file_size"]))
        self.processMap[threadid].setProcessId(threadid + ":" + str(vmInfo["img_file_size"]))
        #self.threadMap[threadid].start()
        #self.processMap[threadid].start()
        #self.connect(self.processMap[threadid], SIGNAL("currentprogress"),self.updateProgressBar)
        #self.connect(self.processMap[threadid], SIGNAL("downloadover"),self.slotDownloadOver)
        
        
    def checkAddDownload(self):
        #return
        
        num = len(self.vmInfoList)
        #existImgList = LocalImgManager.instance().getCompleteList()
        existImgList = LocalImgManager.instance().getCompliteListSize()
        if num == 0:
            return
        else:
            count = 0
            for i in range(len(self.vmInfoList)):
                row = i/3
                column = i%3
                if existImgList.has_key(self.vmInfoList[i]["name"]) and existImgList.get(self.vmInfoList[i]["name"]) == self.vmInfoList[i]["img_file_size"]:
                    pass
                elif self.vmInfoList[i]["name"] in self.downloadingList:
                    pass
                elif existImgList.has_key(self.vmInfoList[i]["name"]) and self.domainManager.getDom() != None and self.startVmInfo != None and self.startVmInfo == self.domainManager.getDomainInfo():
                    pass
                else:
                    if self.vmInfoList[i]["name"] not in self.downloadingList:
                        self.downloadingList.append(self.vmInfoList[i]["name"])
                    threadid = self.vmInfoList[i]["name"] + ":" + QString.number(row) + ":" + QString.number(column)
                    self.createThread(threadid, self.vmInfoList[i])
                    self.setRowColumnWidget(int(row), int(column), self.vmInfoList[i]["name"])
                    count+= 1
                    
            if len(self.threadMap) == count:
                self.startFirstThread()
                if count > 0:
                    self.emit(SIGNAL("startadddownload"))
                
    def startDomain(self): 
        pbp = CustomVMButton().sender()
        buttonName = pbp.objectName()
        vmInfo = pbp.vmInfo
        for item in self.vmOffInfoList:
            if item["name"] == vmInfo["name"]:
                self.startVmInfo = item
        #return
    
        vmid = buttonName.split(":")[0]
        row = buttonName.split(":")[1]
        column = buttonName.split(":")[2]
        
        self.domainManager.setDomainInfo(self.startVmInfo)
        
        LogRecord.instance().logger.info(u"开始开启虚拟机-----------------------------------------------00")
        self.domainManager.startDomainByName()
        LogRecord.instance().logger.info(u"结束开启虚拟机-----------------------------------------------11")
        

    def updateProgressBar(self,count,objectname):
        #if self.threadMap[objectname].isRunning():
#         for i in range(len(self.downloadingList)):
#             if objectname == self.downloadingList[i]:
#                 progress=self.vmTableWidget.findChild((WidgetProgress, ),objectname)
#                 progress.progressBar.setValue(count) 
        progress=self.vmTableWidget.findChild((WidgetProgress, ),objectname)
        if not progress:
            return
        progress.progressBar.setValue(count) 
        
    def slotDownloadOver(self,count,name):
        serverState = VMInfoManager.instance().getCurCloudServerState()
        if serverState != "enable":
            return
        if self.processMap.has_key(name):
            self.processMap[name].stop()
        #self.processMap.pop(name)
        #self.threadMap.pop(name)
        vmid = name.split(":")[0]
        row = name.split(":")[1]
        column = name.split(":")[2]
        self.downloadingList = []
        self.setRowColumnWidget(int(row), int(column), vmid)
        time.sleep(4)
        if self.processMap.has_key(name) and self.processMap[name].isRunning:
            self.processMap[name].exit()
        if self.threadMap.has_key(name) and self.threadMap[name].isRunning:
            self.threadMap[name].exit()
        self.processMap.pop(name)
        self.threadMap.pop(name)
        self.emit(SIGNAL("avmdownloadover"))
#         for item in self.threadMap:
#             self.threadMap[item].start()
#             self.processMap[item].start()
#             self.connect(self.processMap[item], SIGNAL("currentprogress"),self.updateProgressBar)
#             self.connect(self.processMap[item], SIGNAL("downloadover"),self.slotDownloadOver)
#             return
    def getDownloadingList(self):
        return self.downloadingList
            
    def updateVMList(self):
        language = StoreInfoParser.instance().getLanguage()
        m_pTranslator = QTranslator()
        exePath = "./"
        if language == "chinese":
            QmName = "zh_CN.qm"
            StoreInfoParser.instance().setLanguage("chinese")
        else:
            QmName = "en_US.qm"
            
        if(m_pTranslator.load(QmName, exePath)):
            QCoreApplication.instance().installTranslator(m_pTranslator)
        
        if self.vmstatusTimer.isActive():
            self.vmstatusTimer.stop()
        else:
            pass
        subWidget = QWidget()
        vmNum = len(self.vmInfoList)
        rowNum = 0
        subMainLayout = QVBoxLayout()
        subMainLayout.setMargin(0)
        #subMainLayout.addStretch()
        #subMainLayout.addSpacing(10)
        #subMainLayout.setSpacing(10)
        
        self.vmButtonList = []
        
        if vmNum % 3 != 0:
            rowNum = vmNum / 3 + 1
        else:
            rowNum = vmNum / 3
             
        for i in range(rowNum):
            indexEnd = 3
            if i == rowNum - 1:
                indexEnd = vmNum - (i*3)
                 
            parentWidth = self.parent.width()
            resolutionValue = StoreInfoParser.instance().getResolutionValue()
            if resolutionValue:
                if len(resolutionValue.split("x")) >= 2:
                    parentWidth = int(resolutionValue.split("x")[0])*5/9.0
            
            ratio = parentWidth/800.0
            vmWidth = (parentWidth - 20*2 -50*2*ratio)/3
            if vmNum > 3:
                vmWidth = (parentWidth - 20*2 -50*2*ratio)/3 - 20*(1 - ratio)
            vmRowButtonList = []
            for j in range(indexEnd):
                vmButton = CustomVMButton(self)
                vmButton.setFixedSize(QSize(vmWidth, vmWidth + 5))
                vmButton.setIconSize(QSize(vmWidth - 60*ratio,vmWidth - 30*ratio))
                vmButton.setText(self.vmInfoList[i*3 + j]["name"])
                vmButton.setToolTip(self.tr("course: ") + self.vmInfoList[i*3 + j]["name"])
                vmButton.setStyleSheet(u"QToolTip{border-radius:5px;background-color:white;color:black;font-size:20px;font-family:微软雅黑;}")
                vmButton.setFlat(True)
                vmButton.setFont(QFont("Times", 15, QFont.Bold))
                vmInfo = self.vmInfoList[i*3 + j]
                vmButton.setVMInfo(vmInfo)
                imageName = "other.png"
                if self.vmInfoList[i*3 + j].has_key("os_distro"):
                    if common.imageMap.has_key(self.vmInfoList[i*3 + j]["os_distro"]):
                        imageName = common.imageMap[self.vmInfoList[i*3 + j]["os_distro"]]
                    else:
                        LogRecord.instance().logger.info(u'common.imageMap中未找到键值%s' % self.vmInfoList[i*3 + j]["os_distro"])
                        
                vmButton.setIcon(QIcon("images/systemImage/%s" % imageName))
                if globalvariable.CLASS_STATUS and not globalvariable.TICHU_STATUS:
                    self.connect(vmButton, SIGNAL("clicked()"),self.slotOpenVMLesson)
                    self.connect(vmButton, SIGNAL("controlvm"),self.slotControlVM)
                else:
                    self.connect(vmButton, SIGNAL("clicked()"),self.slotCreateVMLesson)
                    
                vmRowButtonList.append(vmButton)
                self.vmButtonList.append(vmButton)
              
            btnLayout = QHBoxLayout()
            if vmNum > 3:
                btnLayout.setSpacing(10)
                #btnLayout.setMargin(10)
                btnLayout.addSpacing(30)
            for vmbtn in vmRowButtonList:
                btnLayout.addWidget(vmbtn)
            btnLayout.addStretch()
            subMainLayout.addLayout(btnLayout) 
             
        #subMainLayout.addStretch()
        subWidget.setLayout(subMainLayout)
        if self.scrollArea.widget():
            self.scrollArea.takeWidget()
        self.scrollArea.setWidgetResizable(False)
        self.scrollArea.setWidget(subWidget)
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    def startVM(self):
        return  
        
    def autoOpenVMLesson(self, vmName, allflag = False):
        LogRecord.instance().logger.info(u'自动打开相应的虚拟机')
        if self.vmButtonList:
            vmInfo = self.vmButtonList[0].getVMInfo()
            VMOperation.instance().setCurrentVMInfo(vmInfo)
            #thread.start_new_thread(VMOperation.instance().openVM,())
            if allflag == True:                      
                if VMOperation.instance().openVM():
                    LogRecord.instance().logger.info(u'start the lesson')  
            else:
                if VMOperation.instance().openVM():
                    LogRecord.instance().logger.info(u'start the lesson')  
        else:
            LogRecord.instance().logger.info(u'未监测到虚拟机%s的信息' % vmName)  
    
    def autoOpenOffVMLesson(self):
        LogRecord.instance().logger.info(u'自动打开相应的虚拟机')
        
        if len(self.vmInfoList) == 1:
            if self.vmInfoList[0]["name"] in self.downloadingList:
                return
            else:
                self.startVmInfo = self.vmInfoList[0]
                self.domainManager.setDomainInfo(self.startVmInfo)
                self.domainManager.startDomainByName()
        
        else:
            return
    def checkDownloadOver(self):
        if self.autoCourse in self.downloadingList:
            pass    
        else:
            self.courseTimer.stop()
            #self.autoCourse = ""
            for item in self.vmOffInfoList:
                if self.autoCourse == item["name"]:
                    vmInfo = item
                    self.domainManager.setDomainInfo(vmInfo)
                    self.domainManager.startDomainByName()
                    return
            
    def autoOpenCourse(self,name,classid = None):
        LogRecord.instance().logger.info(u'自动打开相应的虚拟机')
        self.autoCourse = name
        if name in self.downloadingList:
            self.courseTimer = QTimer()
            self.courseTimer.start(1000)
            self.connect(self.courseTimer, SIGNAL("timeout()"),self.checkDownloadOver)
        
        else:
            for item in self.vmOffInfoList:
                if name == item["name"]:
                    vmInfo = item
                    if classid != None:
                        vmInfo["classid"] = classid
                
                    self.domainManager.setDomainInfo(vmInfo)
                    self.domainManager.startDomainByName()
                    
                    return
                
    def slotCreateVMLesson(self):
        vmBtn = self.sender()
        if vmBtn:
            LogRecord.instance().logger.info(u'开始创建虚拟机')
            if globalvariable.VM_IS_CREATE_STATUS:
                LogRecord.instance().logger.info(u'重复点击!')
                return
            else:
                globalvariable.VM_IS_CREATE_STATUS = True

            LogRecord.instance().logger.info(u'准备获取虚拟机信息!')
            paramInfo = vmBtn.getVMInfo()
            LogRecord.instance().logger.info(u'准备获取虚拟机信息完成!')
            try:
                vmName = VMOperation.instance().createVMLesson(paramInfo)
            except:
                LogRecord.instance().logger.info(u'创建虚拟机异常.........!')
            LogRecord.instance().logger.info(u'得到创建的虚拟机信息!')
            if vmName:
                LogRecord.instance().logger.info(u'创建虚拟机%s成功' % vmName)
                vmInfo = NetworkManager.instance().getVMInfo(vmName)
                if vmInfo == None:
                    globalvariable.VM_IS_CREATE_STATUS = False
                    return
                if len(vmInfo) == 0:
                    globalvariable.VM_IS_CREATE_STATUS = False
                    return
                vmInfo[0]["vmname"] = vmInfo[0]["name"]
                if vmInfo:
                    VMOperation.instance().setCurrentVMInfo(vmInfo[0])
                    if VMOperation.instance().openVM(True):
                        #保存开启虚拟机的名称
                        #globalvariable.VM_IS_CREATE_STATUS = False
                        WindowMonitor.instance().insertVmId(vmName)
                    else:
                        globalvariable.VM_IS_CREATE_STATUS = False
                        #删除没有成功运行的虚拟机
                        if VMOperation.instance().removeVMLesson(vmName):
                            LogRecord.instance().logger.info(u"删除后台相应的虚拟机成功")
                else:
                    LogRecord.instance().logger.info(u'未查询到相应的虚拟机:%s' % vmName)
                    globalvariable.VM_IS_CREATE_STATUS = False
            else:
                LogRecord.instance().logger.info(u'创建虚拟机失败')
                globalvariable.VM_IS_CREATE_STATUS = False


            #刷新虚拟机界面
            self.emit(SIGNAL("refreshVMS"))


    def slotControlVM(self, action):
        vmBtn = self.sender()
        if vmBtn:
            vmInfo = vmBtn.getVMInfo()
            VMOperation.instance().setCurrentVMInfo(vmInfo)
            VMOperation.instance().controlVM(action)
            
            vmInfos = globalvariable.VM_INFO
            self.setVMInfoList(vmInfos,[])
            self.updateVMList()

        
    def slotOpenVMLesson(self):
        #vmInfo = []
        vmBtn = self.sender()
        if vmBtn:
            btnVMInfo = vmBtn.getVMInfo()
            VMOperation.instance().setCurrentVMInfo(btnVMInfo)
            VMOperation.instance().openVM(True)
            
#         if globalvariable.CLASS_STATUS:
#             vmInfo = globalvariable.VM_INFO
#         else:
#             vmInfo = VMInfoManager.instance().getLessonListInfo()
#             
        #self.setVMInfoList(vmInfo,[])
        #self.updateVMList()
    #slider        
    def setSliderMaxValue(self, value):
        """设在滑动条的最大值"""
        maxValue = 1
        if value % 3 == 0:
            maxValue = (value / 3)
        else:
            maxValue = (value / 3 + 1)
         
        maxValue = maxValue - 1
         
        self.slider.setMinimum(1)
        self.slider.setMaximum(maxValue*3)
        self.slider.setValue(self.slider.maximum())
        self.currentIndex = maxValue
        self.maxValue = maxValue
        self.updateVMList()
        
    def paintEvent(self,event):
        if common.SCROLL_TYPE == "slider":
            self.setNodeSize(QSize(self.geometry().width()*4/15,self.geometry().height()*4/9))
        elif len(self.vmInfoList) <= 6:
            subWidget = self.scrollArea.widget()
            if subWidget != None:
                if len(self.vmInfoList) <= 3:
                    subWidget.move((self.geometry().width() - subWidget.width())/2, (self.geometry().height() - subWidget.height())/2 - 20)
                else:
                    subWidget.move((self.geometry().width() - subWidget.width())/2 - 15, (self.geometry().height() - subWidget.height())/2)
        QWidget.paintEvent(self, event)
    
    #slider               
    def setNodeSize(self,size):
        self.btnLayout.setSpacing(size.width()/20)
        self.btnLayout.setMargin(size.width()*3/20)
        
        self.firstVM.setIconSize(size)
        self.secondVM.setIconSize(size)
        self.thirdVM.setIconSize(size)
        
        self.slider.resize(QSize(self.slider.frameGeometry().width(),self.height()-100))
        
    def wheelEvent(self,event):
        """鼠标滚轮滚动事件,显示对应的虚拟机"""
        if common.SCROLL_TYPE == "slider":
            if event.delta() < 0 and self.currentIndex > 0:
                self.currentIndex -= 1
                self.updateVMListSlider()
            elif event.delta() > 0 and self.currentIndex < self.maxValue:
                self.currentIndex += 1
                self.updateVMListSlider()
   
            self.slider.wheelEvent(event)
        else:
            maxValue = self.scrollArea.verticalScrollBar().maximum()
            minValue = self.scrollArea.verticalScrollBar().minimum()
            currentValue = self.scrollArea.verticalScrollBar().value()
            if event.delta() < 0 and currentValue < maxValue:
                self.scrollArea.wheelEvent(event)
            elif event.delta() > 0 and currentValue > minValue:
                self.scrollArea.wheelEvent(event)
        
    #slider     
    def updateVMListSlider(self):
        """更新虚拟机列表"""
        for i in range(len(self.vmButtonList)):
            self.vmButtonList[i].hide()
 
        vmsInfoList = VMInfoManager.instance().getVMSInfo()
        if vmsInfoList:
            self.slider.show()
            startIndex = (self.maxValue - self.currentIndex) * 3
             
            if startIndex/3 == (self.maxValue):
                endIndex = len(vmsInfoList)
            else:
                endIndex = startIndex + 3
                 
            for i in range(startIndex, endIndex):
                self.vmButtonList[i - startIndex].setText(vmsInfoList[i])
                self.vmButtonList[i - startIndex].show()  
        else:
            self.slider.hide()
         
            
            
    """      echo <graphics type='sdl display='$DISPLAY' xauth='$XAUTHORITY'/>
Exemplo n.º 2
0
class ScanRecordTable(QGroupBox):
    """ GUI component. Displays a list of previous scan results. Selecting a scan causes
    details of the scan to appear in other GUI components (list of barcodes in the barcode
    table and image of the puck in the image frame).
    """
    COLUMNS = [
        'Date', 'Time', 'Plate Barcode', 'Plate Type', 'Valid', 'Invalid',
        'Empty'
    ]

    def __init__(self, barcode_table, image_frame, options,
                 to_run_on_table_clicked):
        super(ScanRecordTable, self).__init__()

        # Read the store from file
        self._store = Store(options.store_directory.value(),
                            options.store_capacity, FileManager())
        self._options = options

        self._barcodeTable = barcode_table
        self._imageFrame = image_frame

        self.setTitle("Scan Records")
        self._init_ui(to_run_on_table_clicked)

        self._load_store_records()

    def _init_ui(self, to_run_on_table_clicked):
        # Create record table - lists all the records in the store
        self._table = QTableWidget()
        self._table.setFixedWidth(440)
        self._table.setFixedHeight(600)
        self._table.setColumnCount(len(self.COLUMNS))
        self._table.setHorizontalHeaderLabels(self.COLUMNS)
        self._table.setColumnWidth(0, 70)
        self._table.setColumnWidth(1, 55)
        self._table.setColumnWidth(2, 85)
        self._table.setColumnWidth(3, 70)
        self._table.setColumnWidth(4, 45)
        self._table.setColumnWidth(5, 50)
        self._table.setColumnWidth(6, 45)
        self._table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self._table.cellPressed.connect(to_run_on_table_clicked)
        self._table.cellPressed.connect(self._record_selected)

        # Delete button - deletes selected records
        btn_delete = QtGui.QPushButton('Delete')
        btn_delete.setToolTip('Delete selected scan/s')
        btn_delete.resize(btn_delete.sizeHint())
        btn_delete.clicked.connect(self._delete_selected_records)

        hbox = QHBoxLayout()
        hbox.setSpacing(10)
        hbox.addWidget(btn_delete)
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addWidget(self._table)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    def add_record_frame(self, holder_barcode, plate, holder_img, pins_img):
        """ Add a new scan frame - creates a new record if its a new puck, else merges with previous record"""
        self._store.merge_record(holder_barcode, plate, holder_img, pins_img)
        self._load_store_records()
        if self._options.scan_clipboard.value():
            self._barcodeTable.copy_to_clipboard()

    def _load_store_records(self):
        """ Populate the record table with all of the records in the store.
        """
        self._table.clearContents()
        self._table.setRowCount(self._store.size())

        for n, record in enumerate(self._store.records):
            items = [
                record.date, record.time, record.holder_barcode,
                record.plate_type, record.num_valid_barcodes,
                record.num_unread_slots, record.num_empty_slots
            ]

            if (record.num_valid_barcodes +
                    record.num_empty_slots) == record.num_slots:
                color = self._options.col_ok()
            else:
                color = self._options.col_bad()

            color.a = 192
            for m, item in enumerate(items):
                new_item = QtGui.QTableWidgetItem(str(item))
                new_item.setBackgroundColor(color.to_qt())
                new_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                self._table.setItem(n, m, new_item)

        # Display the first (most recent) record
        self._table.setCurrentCell(0, 0)
        self._record_selected()

    def _record_selected(self):
        """ Called when a row is selected, causes details of the selected record to be
        displayed (list of barcodes in the barcode table and image of the scan in the
        image frame).
        """
        try:
            row = self._table.selectionModel().selectedRows()[0].row()
            record = self._store.get_record(row)
            self._barcodeTable.populate(record.holder_barcode, record.barcodes)
            marked_image = record.marked_image(self._options)
            self._imageFrame.display_puck_image(marked_image)
        except IndexError:
            self._barcodeTable.clear()
            self._imageFrame.clear_frame(
                "Record table empty\nNothing to display")

    def _delete_selected_records(self):
        """ Called when the 'Delete' button is pressed. Deletes all of the selected records
        (and the associated images) from the store and from disk. Asks for user confirmation.
        """
        # Display a confirmation dialog to check that user wants to proceed with deletion
        quit_msg = "This operation cannot be undone.\nAre you sure you want to delete these record/s?"
        reply = QtGui.QMessageBox.warning(self, 'Confirm Delete', quit_msg,
                                          QtGui.QMessageBox.Yes,
                                          QtGui.QMessageBox.No)

        # If yes, find the appropriate records and delete them
        if reply == QtGui.QMessageBox.Yes:
            rows = self._table.selectionModel().selectedRows()
            records_to_delete = []
            for row in rows:
                index = row.row()
                record = self._store.get_record(index)
                records_to_delete.append(record)

            self._store.delete_records(records_to_delete)
            self._load_store_records()

    def is_latest_holder_barcode(self, holder_barcode):
        return self._store.is_latest_holder_barcode(holder_barcode)
Exemplo n.º 3
0
class RemoveSentencesDlg(QDialog):
	
	def __init__(self):
		super(RemoveSentencesDlg, self).__init__()
		self.removeList = {}
		self.initUI()
		
	def initUI(self):
		## create a font type for the label
		fontLabel = QFont('SansSerif', 14)
		## create a label 
		self.label = QLabel(self)
		self.label.setAutoFillBackground(True)
		self.label.setAlignment(Qt.AlignCenter)
		self.label.setGeometry(QRect(200, 10, 400, 40))
		self.label.setFont(fontLabel)
		
		## set the text for the Label
		self.label.setText('Remove the Sentences')
		
		## Create a Table to hold all the sentences
		self.sentenceTable = QTableWidget(self)
		## set the size and the position of the table
		self.sentenceTable.setGeometry(QRect(10, 60, 800, 400))
		
		## set the column count for the table
		self.sentenceTable.setColumnCount(1)
		
		sentenceHeaderList = ['Sentences From Table']
		
		self.sentenceTable.setHorizontalHeaderLabels(sentenceHeaderList)
		self.sentenceTable.resizeColumnsToContents()
		self.sentenceTable.horizontalHeader().setStretchLastSection(True)
		self.sentenceTable.verticalHeader().setStretchLastSection(True)
		
		# Create a Remove button
		self.removeButton = QPushButton('Remove', self)
		self.removeButton.move(350, 600)
		
		# Create a close button
		self.closeButton = QPushButton('Close', self)
		self.closeButton.move(450, 600)
		
		## Create signal for the remove button 
		self.connect(self.removeButton, SIGNAL("clicked()"), self.onRemoveClicked)
		
		## Create signal for the close button
		self.connect(self.closeButton, SIGNAL("clicked()"), self.closeClicked)
		
		## Create signal for the sentence table
		self.connect(self.sentenceTable, SIGNAL('cellClicked(int, int)'), self.sentenceStateChanged)
		
		## Get sentences from the database table
		self.getSentencesFromDatabase()
		
		
		self.setGeometry(800, 800, 900, 650)
		
	def getSentencesFromDatabase(self):
		## returns the sentence and the corresponding word associated with it
		self.twoTypeList = data.Data().getSentencesFromDatabase()
		if len(self.twoTypeList) == 2:
			self.sentenceList = self.twoTypeList[0]
			
		## load the sentences in the Table Widget
		self.loadSentencesInTable()
	
	def loadSentencesInTable(self):
		self.col = 0
		if len(self.sentenceList) > 0:
			self.sentenceTable.setRowCount(len(self.sentenceList))
			cellList = []
			## Create QTableWidgetItems from the sentence list
			for sentence in self.sentenceList:
				item = QTableWidgetItem(QString(sentence))
				item.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable |
								Qt.ItemIsUserCheckable | Qt.ItemIsEnabled))
				item.setCheckState(Qt.Unchecked)
				cellList.append(item)
			cellList.reverse()
			# set the widget items in the table
			for item in range(0, len(self.sentenceList)):
				self.sentenceTable.setItem(item, self.col, cellList.pop())
			
			for index in range(0, len(self.sentenceList)):
				self.sentenceTable.verticalHeader().resizeSection(index, 100)
			
			if len(self.sentenceList) == 5:
				self.sentenceTable.setFixedHeight(500)
			if len(self.sentenceList) == 4:
				self.sentenceTable.setFixedHeight(400)
			elif len(self.sentenceList) == 3:
				self.sentenceTable.setFixedHeight(300)
			elif len(self.sentenceList) == 2:
				self.sentenceTable.setFixedHeight(200)
			elif len(self.sentenceList) == 1:
				self.sentenceTable.setFixedHeight(100)
		else:
			self.sentenceTable.clearContents()
		
	def sentenceStateChanged(self, row, col):
		if (self.sentenceTable.item(row, col).checkState() == 2):
			self.removeList[row] = self.sentenceTable.item(row, col).text()
			self.sentenceTable.item(row, col).setFlags(Qt.ItemFlags(Qt.ItemIsEditable |
									Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable))
		else:
			self.sentenceTable.item(row,col).setFlags(Qt.ItemFlags(Qt.ItemIsSelectable |
									Qt.ItemIsUserCheckable | Qt.ItemIsEnabled))
			del self.removeList[row]
	
	def onRemoveClicked(self):
		if len(self.removeList) == 0:
			QMessageBox.information(self, 'Remove Sentences',
					'Select the Sentences to Delete')
		else:
			reply = QMessageBox.question(self,
						'Remove Sentences',
						'You want to delete the selected sentences',
						QMessageBox.Yes | QMessageBox.No)
			if reply == QMessageBox.Yes:
				for sentence in self.removeList:
					if sentence in self.sentenceList:
						self.sentenceList.remove(sentence)
				
				## clear the contents of the Table
				self.sentenceTable.clearContents()
				
				cellList = []
				## Add the new entries into the table
				for sentence in self.sentenceList:
					item = QTableWidgetItem(QString(sentence))
					item.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable |
									Qt.ItemIsUserCheckable | Qt.ItemIsEnabled))
					item.setCheckState(Qt.Unchecked)
					cellList.append(item)
				cellList.reverse()
				
				for index in range(0, len(self.sentenceList)):
					self.sentenceTable.setItem(index, self.col, cellList.pop())
					
				self.sentenceTable.setRowCount(len(self.sentenceList))
				
				deleteSentences = []
				for sentence in self.removeList.values():
					deleteSentences.append(sentence)
				
				rowsDeletec = data.Data().deleteSentencesFromDatabase(deleteSentences)
				self.getSentencesFromDatabase()
				
				self.removeList = {}
	
	def closeClicked(self):
		reply = QMessageBox.question(self,
				"Remove Sentences",
				"Do you want to close the application",
				QMessageBox.Yes | QMessageBox.No)
		if reply == QMessageBox.Yes:
			self.reject()
Exemplo n.º 4
0
class daq_window(QMainWindow):
    """Window to control and visualise DAQ measurements.
    Set up the desired channels with a given sampling rate.
    Start an acquisition after a trigger. 
    Display the acquired data on a trace, and accumulated
    data on a graph.
    
    Arguments:
    n       -- run number for synchronisation
    rate    -- max sample rate in samples / second
    dt      -- desired acquisition period in seconds
    config_file -- path to file storing default settings
    port    -- the port number to open for TCP connections
    """
    def __init__(self, n=0, rate=250, dt=500, config_file='monitor\\daqconfig.dat', port=8622):
        super().__init__()
        self.types = OrderedDict([('n', int), ('config_file', str), ('trace_file', str), ('graph_file', str),
            ('save_dir', str), ('Sample Rate (kS/s)',float), 
            ('Duration (ms)', float), ('Trigger Channel', str), ('Trigger Level (V)', float), 
            ('Trigger Edge', str), ('channels',channel_stats)])
        self.stats = OrderedDict([('n', n), ('config_file', config_file), ('trace_file', 'DAQtrace.csv'), 
            ('graph_file', 'DAQgraph.csv'),('save_dir', '.'), ('Sample Rate (kS/s)', rate), 
            ('Duration (ms)', dt), ('Trigger Channel', 'Dev2/ai1'), # /Dev2/PFI0
            ('Trigger Level (V)', 1.0), ('Trigger Edge', 'rising'), 
            ('channels', channel_stats("[['Dev2/ai0', '0', '1.0', '0.0', '5', '1', '1']]"))])
        self.trigger_toggle = True       # whether to trigger acquisition or just take a measurement
        self.slave = worker(rate*1e3, dt/1e3, self.stats['Trigger Channel'], 
                self.stats['Trigger Level (V)'], self.stats['Trigger Edge'], list(self.stats['channels'].keys()), 
                [ch['range'] for ch in self.stats['channels'].values()]) # this controls the DAQ
        self.dc = daqCollection(param=[], channels=list(self.stats['channels'].keys()))
        self.init_UI()
        self.load_config(config_file)    # load default settings          
        self.n_samples = int(self.stats['Duration (ms)'] * self.stats['Sample Rate (kS/s)']) # number of samples per acquisition
        self.last_path = './'

        self.x = [] # run numbers for graphing collections of acquired data
        self.y = [] # average voltages in slice of acquired trace 

        self.slave.acquired.connect(self.update_graph) # take average of slices
        self.slave.acquired.connect(self.update_trace) # plot new data when it arrives
        self.tcp = PyClient(port=port)
        remove_slot(self.tcp.dxnum, self.set_n, True)
        remove_slot(self.tcp.textin, self.respond, True)
        self.tcp.start()

    def init_UI(self):
        """Produce the widgets and buttons."""
        self.centre_widget = QWidget()
        self.tabs = QTabWidget()       # make tabs for each main display 
        self.centre_widget.layout = QVBoxLayout()
        self.centre_widget.layout.addWidget(self.tabs)
        self.centre_widget.setLayout(self.centre_widget.layout)
        self.setCentralWidget(self.centre_widget)
        
        # change font size
        font = QFont()
        font.setPixelSize(18)

        #### menubar at top gives options ####
        menubar = self.menuBar()

        # file menubar allows you to save/load data
        file_menu = menubar.addMenu('File')
        for label, function in [['Load Config', self.load_config],
                ['Save Config', self.save_config],
                ['Load Trace', self.load_trace], 
                ['Save Trace', self.save_trace], 
                ['Save Graph', self.save_graph]]:
            action = QAction(label, self) 
            action.triggered.connect(function)
            file_menu.addAction(action)

        #### tab for settings  ####
        settings_tab = QWidget()
        settings_grid = QGridLayout()
        settings_tab.setLayout(settings_grid)
        self.tabs.addTab(settings_tab, "Settings")

        self.settings = QTableWidget(1, 6)
        self.settings.setHorizontalHeaderLabels(['Duration (ms)', 
            'Sample Rate (kS/s)', 'Trigger Channel', 'Trigger Level (V)', 
            'Trigger Edge', 'Use Trigger?'])
        settings_grid.addWidget(self.settings, 0,0, 1,1)
        defaults = [str(self.stats['Duration (ms)']), str(self.stats['Sample Rate (kS/s)']), 
            self.stats['Trigger Channel'], str(self.stats['Trigger Level (V)']), 
            self.stats['Trigger Edge'], '1']
        validators = [double_validator, double_validator, None, double_validator, None, bool_validator]
        for i in range(6):
            table_item = QLineEdit(defaults[i]) # user can edit text to change the setting
            if defaults[i] == 'Sample Rate (kS/s)': table_item.setEnabled(False)
            table_item.setValidator(validators[i]) # validator limits the values that can be entered
            self.settings.setCellWidget(0,i, table_item)
        self.settings.resizeColumnToContents(1) 
        self.settings.setFixedHeight(70) # make it take up less space
        self.settings.cellWidget(0,0).textChanged.connect(self.check_slice_duration)
                    
        # start/stop: start waiting for a trigger or taking an acquisition
        self.toggle = QPushButton('Start', self)
        self.toggle.setCheckable(True)
        self.toggle.clicked.connect(self.activate)
        settings_grid.addWidget(self.toggle, 1,0, 1,1)

        # channels
        self.channels = QTableWidget(8, 7) # make table
        self.channels.setHorizontalHeaderLabels(['Channel', 'Label', 
            'Scale (X/V)', 'Offset (V)', 'Range', 'Acquire?', 'Plot?'])
        settings_grid.addWidget(self.channels, 2,0, 1,1) 
        validators = [None, double_validator, double_validator, None, bool_validator, bool_validator]
        for i in range(8):
            chan = 'Dev2/ai'+str(i)  # name of virtual channel
            table_item = QLabel(chan)
            self.channels.setCellWidget(i,0, table_item)
            if chan in self.stats['channels']: # load values from previous
                defaults = self.stats['channels'][chan]
            else: # default values when none are loaded
                defaults = channel_stats("[dummy, "+str(i)+", 1.0, 0.0, 5.0, 0, 0]")['dummy']
            for j, key in zip([0,1,2,4,5], ['label', 'scale', 'offset', 'acquire', 'plot']):
                table_item = QLineEdit(str(defaults[key]))
                if 'acquire' in key:
                    table_item.textChanged.connect(self.check_slice_channels)
                elif 'plot' in key:
                    table_item.textChanged.connect(self.set_acquire)
                table_item.setValidator(validators[j])        
                self.channels.setCellWidget(i,j+1, table_item)
            vrange = QComboBox() # only allow certain values for voltage range
            vrange.text = vrange.currentText # overload function so it's same as QLabel
            vrange.addItems(['%.1f'%x for x in self.slave.vrs])
            try: vrange.setCurrentIndex(self.slave.vrs.index(defaults['range']))
            except Exception as e: logger.error('Invalid channel voltage range\n'+str(e))
            self.channels.setCellWidget(i,4, vrange)

        #### Plot for most recently acquired trace ####
        trace_tab = QWidget()
        trace_grid = QGridLayout()
        trace_tab.setLayout(trace_grid)
        self.tabs.addTab(trace_tab, "Trace")
        
        # button activates horizontal line
        self.hline_toggle = QPushButton('Horizontal line', self, checkable=True)
        self.hline_toggle.clicked.connect(self.add_horizontal)
        trace_grid.addWidget(self.hline_toggle, 0,0, 1,1)
        self.hline_label = QLabel()
        trace_grid.addWidget(self.hline_label, 0,1, 1,1)
        fadeline_button = QPushButton('Persist', self)
        fadeline_button.clicked.connect(self.set_fadelines)
        trace_grid.addWidget(fadeline_button, 0,2, 1,1)
        

        # plot the trace
        self.trace_canvas = pg.PlotWidget()
        self.trace_legend = self.trace_canvas.addLegend()
        self.trace_canvas.getAxis('bottom').tickFont = font
        self.trace_canvas.getAxis('left').tickFont = font
        self.trace_canvas.setLabel('bottom', 'Time', 's', **{'font-size':'18pt'})
        self.trace_canvas.setLabel('left', 'Voltage', 'V', **{'font-size':'18pt'})
        self.lines = [] # handles for lines plotting the last measurement
        self.fadelines = [] # handles for previous measurement lines
        for i in range(8):
            chan = self.channels.cellWidget(i,1).text()
            self.lines.append(self.trace_canvas.plot([1], name=chan, 
                    pen=pg.mkPen(pg.intColor(i), width=3)))
            self.lines[i].hide()
            self.fadelines.append(self.trace_canvas.plot([1], 
                    pen=pg.mkPen(pg.intColor(i, alpha=50), width=2)))
            self.fadelines[i].hide()
        self.hline = pg.InfiniteLine(1., angle=0, pen='k', movable=True)
        self.trace_canvas.addItem(self.hline)
        self.hline.sigPositionChanged.connect(self.update_hline)
        self.hline.hide()
        
            
        trace_grid.addWidget(self.trace_canvas, 1,0, 1,3)
        
        #### Settings for slices of the trace accumulating into the graph ####
        slice_tab = QWidget()
        slice_grid = QGridLayout()
        slice_tab.setLayout(slice_grid)
        self.tabs.addTab(slice_tab, "Slice")
        
        # Buttons to add/remove slices and reset graph
        for i, (label, func) in enumerate([['Add slice', self.add_slice],
                ['Remove slice', self.del_slice], ['Reset graph', self.reset_graph]]):
            button = QPushButton(label, self)
            button.clicked.connect(func)
            slice_grid.addWidget(button, 0,i, 1,1)
        
        # parameters for slices
        self.slices = QTableWidget(0, 4) # make table
        self.slices.setHorizontalHeaderLabels(['Slice name', 
            'Start (ms)', 'End (ms)', 'Channels'])
        slice_grid.addWidget(self.slices, 1,0, 1,3) 

        #### Plot for graph of accumulated data ####
        graph_tab = QWidget()
        graph_grid = QGridLayout()
        graph_tab.setLayout(graph_grid)
        self.tabs.addTab(graph_tab, "Graph")

        self.mean_graph = pg.PlotWidget() # for plotting means
        self.stdv_graph = pg.PlotWidget() # for plotting standard deviations
        self.graph_legends = []
        for i, g in enumerate([self.mean_graph, self.stdv_graph]):
            g.getAxis('bottom').tickFont = font
            g.getAxis('bottom').setFont(font)
            g.getAxis('left').tickFont = font
            g.getAxis('left').setFont(font)
            graph_grid.addWidget(g, i,0, 1,1)
        self.reset_lines() # make a line for every slice channel
        self.stdv_graph.setLabel('bottom', 'Shot', '', **{'font-size':'18pt'})
        self.stdv_graph.setLabel('left', 'Standard Deviation', 'V', **{'font-size':'18pt'})
        self.mean_graph.setLabel('left', 'Mean', 'V', **{'font-size':'18pt'})
        
        #### tab for TCP message settings  ####
        tcp_tab = QWidget()
        tcp_grid = QGridLayout()
        tcp_tab.setLayout(tcp_grid)
        self.tabs.addTab(tcp_tab, "Sync")

        label = QLabel('Run number: ')
        tcp_grid.addWidget(label, 0,0, 1,1)
        self.n_edit = QLineEdit(str(self.stats['n']))
        self.n_edit.setValidator(int_validator)
        self.n_edit.textEdited[str].connect(self.set_n)
        tcp_grid.addWidget(self.n_edit, 0,1, 1,1)
        
        label = QLabel('Save directory: ')
        tcp_grid.addWidget(label, 1,0, 1,1)
        self.save_edit = QLineEdit(self.stats['save_dir'])
        self.save_edit.textEdited[str].connect(self.set_save_dir)
        tcp_grid.addWidget(self.save_edit, 1,1, 1,1)
        
        label = QLabel('Trace file name: ')
        tcp_grid.addWidget(label, 2,0, 1,1)
        self.trace_edit = QLineEdit(self.stats['trace_file'])
        self.trace_edit.textEdited[str].connect(self.set_trace_file)
        tcp_grid.addWidget(self.trace_edit, 2,1, 1,1)
        
        label = QLabel('Graph file name: ')
        tcp_grid.addWidget(label, 3,0, 1,1)
        self.graph_edit = QLineEdit(self.stats['graph_file'])
        self.graph_edit.textEdited[str].connect(self.set_graph_file)
        tcp_grid.addWidget(self.graph_edit, 3,1, 1,1)
        
        reset = QPushButton('Reset TCP client', self)
        reset.clicked.connect(self.reset_client)
        tcp_grid.addWidget(reset, 4,0, 1,1)

        #### Title and icon ####
        self.setWindowTitle('- NI DAQ Controller -')
        self.setWindowIcon(QIcon('docs/daqicon.png'))
        self.setGeometry(200, 200, 800, 600)

    #### user input functions ####

    def set_acquire(self):
        """If the user chooses to plot, set the same channel to acquire."""
        for i in range(self.channels.rowCount()):
            if BOOL(self.channels.cellWidget(i,6).text()): # plot
                self.channels.cellWidget(i,5).setText('1') # only plot if acquiring
    
    def check_settings(self):
        """Coerce the settings into allowed values."""
        statstr = "[[" # dictionary of channel names and properties
        for i in range(self.channels.rowCount()):
            self.trace_legend.items[i][1].setText(self.channels.cellWidget(i,1).text()) # label
            if BOOL(self.channels.cellWidget(i,5).text()): # acquire
                statstr += ', '.join([self.channels.cellWidget(i,j).text() 
                    for j in range(self.channels.columnCount())]) + '],['
        self.stats['channels'] = channel_stats(statstr[:-2] + ']')
        self.dc.channels = self.stats['channels'].keys()

        # acquisition settings
        self.stats['Duration (ms)'] = float(self.settings.cellWidget(0,0).text())
        # check that the requested rate is valid
        rate = float(self.settings.cellWidget(0,1).text())
        if len(self.stats['channels']) > 1 and rate > 245 / len(self.stats['channels']):
            rate = 245 / len(self.stats['channels'])
        elif len(self.stats['channels']) < 2 and rate > 250:
            rate = 250
        self.stats['Sample Rate (kS/s)'] = rate
        self.settings.cellWidget(0,1).setText('%.2f'%(rate))
        self.n_samples = int(self.stats['Duration (ms)'] * self.stats['Sample Rate (kS/s)'])
        # check the trigger channel is valid
        trig_chan = self.settings.cellWidget(0,2).text() 
        if 'Dev2/PFI' in trig_chan or 'Dev2/ai' in trig_chan:
            self.stats['Trigger Channel'] = trig_chan
        else: 
            self.stats['Trigger Channel'] = 'Dev2/ai0'
        self.settings.cellWidget(0,2).setText(str(self.stats['Trigger Channel']))
        self.stats['Trigger Level (V)'] = float(self.settings.cellWidget(0,3).text())
        self.stats['Trigger Edge'] = self.settings.cellWidget(0,4).text()
        self.trigger_toggle = BOOL(self.settings.cellWidget(0,5).text())
        
        
    def set_table(self):
        """Display the acquisition and channel settings in the table."""
        x = self.stats.copy() # prevent it getting overwritten
        for i in range(5):
            self.settings.cellWidget(0,i).setText(str(x[
                self.settings.horizontalHeaderItem(i).text()]))
        for i in range(8):
            ch = self.channels.cellWidget(i,0).text()
            if ch in x['channels']:
                for j, key in zip([0,1,2,4,5], 
                        ['label', 'scale', 'offset', 'acquire', 'plot']):
                    self.channels.cellWidget(i,j+1).setText(str(x['channels'][ch][key]))
                self.channels.cellWidget(i,4).setCurrentText('%.1f'%x['channels'][ch]['range'])
            else:
                self.channels.cellWidget(i,5).setText('0') # don't acquire
                self.channels.cellWidget(i,6).setText('0') # don't plot

    #### slice settings functions ####

    def add_slice(self, param=[]):
        """Add a row to the slice table and append it to the
        daqCollection instance for analysis.
        param -- [name, start (ms), end (ms), channels]"""
        try:
            name, start, end, channels = param
        except TypeError:
            name = 'Slice' + str(self.slices.rowCount())
            start = 0
            end = self.stats['Duration (ms)']
            channels = list(self.stats['channels'].keys())
        i = self.slices.rowCount() # index to add row at
        self.slices.insertRow(i) # add row to table
        validator = QDoubleValidator(0.,float(self.stats['Duration (ms)']),3)
        for j, text in enumerate([name, str(start), str(end)]):
            item = QLineEdit(text)
            item.pos = (i, j)
            if j > 0:
                item.setValidator(validator)
            item.textChanged.connect(self.update_slices)
            self.slices.setCellWidget(i, j, item)
        chanbox = QListWidget(self)
        chanbox.setSelectionMode(3) # extended selection, allows multiple selection
        chanbox.itemSelectionChanged.connect(self.update_slices)
        chanbox.pos = (i, 3)
        chanbox.text = chanbox.objectName
        chanlist = list(self.stats['channels'].keys())
        chanbox.addItems(chanlist)
        self.slices.setCellWidget(i, j+1, chanbox)
        self.slices.resizeRowToContents(i) 
        # add to the dc list of slices
        t = np.linspace(0, self.stats['Duration (ms)']/1000, self.n_samples)
        self.dc.add_slice(name, np.argmin(np.abs(t-start)), np.argmin(np.abs(t-end)), 
            OrderedDict([(chan, chanlist.index(chan)) for chan in channels]))
            
        self.reset_lines()
            

    def del_slice(self, toggle=True):
        """Remove the slice at the selected row of the slice table."""
        index = self.slices.currentRow()
        self.slices.removeRow(index)
        if index >= 0:
            self.dc.slices.pop(index)
        
    def check_slice_duration(self, newtxt):
        """If the acquisition duration is changed, make sure the slices can't
        use times beyond this."""
        self.check_settings() # update DAQ acquisition settings first
        for i in range(self.slices.rowCount()):
            for j in [1,2]: # force slice to be within max duration
                validator = QDoubleValidator(0.,float(self.stats['Duration (ms)']),3)
                self.slices.cellWidget(i, j).setValidator(validator)

    def check_slice_channels(self, newtxt):
        """If the channels that can be used for the acquisition are changed, 
        change the list widgets in the slice settings to match."""
        self.check_settings() # update DAQ acquisition settings first
        for i in range(self.slices.rowCount()):
            w = self.slices.cellWidget(i, 3) # widget shorthand
            selected = [w.row(x) for x in w.selectedItems()] # make record of selected channels
            w.clear()
            w.addItems(list(self.stats['channels'].keys()))
            for r in selected:
                try:
                    w.setCurrentRow(r, QItemSelectionModel.SelectCurrent)
                except Exception as e: pass

    def update_slices(self, newtxt=''):
        """Use the current item from the table to update the parameter for the slices."""
        try:
            w = self.sender()
            i, j = w.pos
            t = np.linspace(0, self.stats['Duration (ms)'], self.n_samples)
            x = self.dc.slices[i] # shorthand
            if j == 0: # name
                x.name = w.text()
            elif j == 1: # start (ms)
                x.i0 = np.argmin(np.abs(t-float(w.text())))
            elif j == 2: # end (ms)
                x.i1 = np.argmin(np.abs(t-float(w.text())))
            elif j == 3:
                x.channels = OrderedDict([(x.text(), w.row(x)) for x in w.selectedItems()])
                x.stats = OrderedDict([(chan, OrderedDict([
                    ('mean',[]), ('stdv',[])])) for chan in x.channels.keys()])
                self.reset_lines()
                self.reset_graph()
            x.inds = slice(x.i0, x.i1+1)
            x.size = x.i1 - x.i0
        except IndexError as e: pass # logger.error("Couldn't update slice.\n"+str(e))    

    #### TCP functions ####
    
    def set_n(self, num):
        """Receive the new run number to update to"""
        self.stats['n'] = int(num)
        self.n_edit.setText(str(num))
        
    def set_save_dir(self, directory):
        """Set the directory to save results to"""
        self.stats['save_dir'] = directory
        self.save_edit.setText(directory)
        
    def set_trace_file(self, fname):
        """Set the default name for trace files when they're saved."""
        self.stats['trace_file'] = fname
        self.trace_edit.setText(fname)
    
    def set_graph_file(self, fname):
        """Set the default name for graph files when they're saved."""
        self.stats['graph_file'] = fname
        self.graph_edit.setText(fname)
        
    def reset_client(self, toggle=True):
        """Stop the TCP client thread then restart it."""
        self.tcp.stop = True
        for i in range(100): # wait til it's stopped
            if not self.tcp.isRunning():
                break
            else: time.sleep(0.001)
        self.tcp.start() # restart
        
    def respond(self, msg=''):
        """Interpret a TCP message. For setting properties, the syntax is:
        value=property. E.g. 'Z:\Tweezer=save_dir'."""
        if 'save_dir' in msg: 
            self.set_save_dir(msg.split('=')[0])
        elif 'trace_file' in msg:
            self.set_trace_file(msg.split('=')[0])
        elif 'graph_file' in msg:
            self.set_graph_file(msg.split('=')[0])
        elif 'start' in msg and not self.toggle.isChecked():
            self.toggle.setChecked(True)
            self.activate()
        elif 'stop' in msg and self.toggle.isChecked():
            self.toggle.setChecked(False)
            self.activate()
        elif 'save trace' in msg:
            self.save_trace(os.path.join(self.stats['save_dir'], self.stats['trace_file']))
        elif 'save graph' in msg:
            self.save_graph(os.path.join(self.stats['save_dir'], self.stats['graph_file']))
        elif 'set fadelines' in msg:
            self.set_fadelines()
    
    #### acquisition functions #### 

    def activate(self, toggle=0):
        """Prime the DAQ task for acquisition if it isn't already running.
        Otherwise, stop the task running."""
        if self.toggle.isChecked():
            self.check_settings()
            self.slave = worker(self.stats['Sample Rate (kS/s)']*1e3, self.stats['Duration (ms)']/1e3, self.stats['Trigger Channel'], 
                self.stats['Trigger Level (V)'], self.stats['Trigger Edge'], list(self.stats['channels'].keys()), 
                [ch['range'] for ch in self.stats['channels'].values()])
            remove_slot(self.slave.acquired, self.update_trace, True)
            remove_slot(self.slave.acquired, self.update_graph, True)
            if self.trigger_toggle:
                # remove_slot(self.slave.finished, self.activate, True)
                self.slave.start()
                self.toggle.setText('Stop')
            else: 
                self.toggle.setChecked(False)
                self.slave.analogue_acquisition()
        else:
            # remove_slot(self.slave.finished, self.activate, False)
            self.slave.stop = True
            self.slave.quit()
            self.toggle.setText('Start')

    #### plotting functions ####

    def update_trace(self, data):
        """Plot the supplied data with labels on the trace canvas."""
        t = np.linspace(0, self.stats['Duration (ms)']/1000, self.n_samples)
        i = 0 # index to keep track of which channels have been plotted
        for j in range(8):
            ch = self.channels.cellWidget(j,0).text()
            l = self.lines[j] # shorthand
            if ch in self.stats['channels'] and self.stats['channels'][ch]['plot']:
                try:
                    l.setData(t, data[i])
                except Exception as e:
                    logger.error('DAQ trace could not be plotted.\n'+str(e))
                self.fadelines[j].show()
                l.show()
                self.trace_legend.items[j][0].show()
                self.trace_legend.items[j][1].show()
                i += 1
            else:
                l.hide()
                self.fadelines[j].hide()
                self.trace_legend.items[j][0].hide()
                self.trace_legend.items[j][1].hide()
        self.trace_legend.resize(0,0)
        
    def set_fadelines(self):
        """Take the data from the current lines and sets it to the fadelines."""
        for j in range(8):
            ch = self.channels.cellWidget(j,0).text()
            l = self.lines[j] # shorthand
            if ch in self.stats['channels'] and self.stats['channels'][ch]['plot']:
                try:
                    self.fadelines[j].setData(l.xData, l.yData)
                except Exception as e:
                    logger.error('DAQ trace could not be plotted.\n'+str(e))
                self.fadelines[j].show()
            else:
                self.fadelines[j].hide()
        
    def reset_lines(self):
        """Clear the mean and stdv graphs, reset the legends, then make new 
        lines for each of the slice channels."""
        for legend in self.graph_legends: # reset the legends
            try:
                legend.scene().removeItem(legend)
            except AttributeError: pass
        for g in [self.mean_graph, self.stdv_graph]:
            g.clear()
            g.lines = OrderedDict([])
            self.graph_legends.append(g.addLegend())
            i = 0
            for s in self.dc.slices:
                for chan, val in s.stats.items():
                    g.lines[s.name+'/'+chan] = g.plot([1], name=s.name+'/'+chan, 
                        pen=None, symbol='o', symbolPen=pg.mkPen(pg.intColor(i)),
                        symbolBrush=pg.intColor(i)) 
                    i += 1

    def reset_graph(self):
        """Reset the collection of slice data, then replot the graph."""
        self.dc.reset_arrays()
        self.update_graph()

    def update_graph(self, data=[]):
        """Extract averages from slices of the data.
        Replot the stored data accumulated from averages in slices
        of the measurements."""
        if np.size(data):
            self.dc.process(data, self.stats['n'])
        for s in self.dc.slices:
            for chan, val in s.stats.items():
                self.mean_graph.lines[s.name+'/'+chan].setData(self.dc.runs, val['mean'])
                self.stdv_graph.lines[s.name+'/'+chan].setData(self.dc.runs, val['stdv'])
                
    def add_horizontal(self, toggle=True):
        """Display a horizontal line on the trace"""
        if toggle: self.hline.show()
        else: 
            self.hline.hide()
            self.hline_label.setText('')
        
    def update_hline(self):
        """Display the value of the horizontal line in the label"""
        self.hline_label.setText(str(self.hline.value()))

    #### save/load functions ####

    def try_browse(self, title='Select a File', file_type='all (*)', 
                open_func=QFileDialog.getOpenFileName, default_path=''):
        """Open a file dialog and retrieve a file name from the browser.
        title: String to display at the top of the file browser window
        default_path: directory to open first
        file_type: types of files that can be selected
        open_func: the function to use to open the file browser"""
        default_path = default_path if default_path else os.path.dirname(self.last_path)
        try:
            if 'PyQt4' in sys.modules:
                file_name = open_func(self, title, default_path, file_type)
            elif 'PyQt5' in sys.modules:
                file_name, _ = open_func(self, title, default_path, file_type)
            if type(file_name) == str: self.last_path = file_name 
            return file_name
        except OSError: return '' # probably user cancelled

    def save_config(self, file_name='daqconfig.dat'):
        """Save the current acquisition settings to the config file."""
        self.stats['config_file'] = file_name if file_name else self.try_browse(
                'Save Config File', 'dat (*.dat);;all (*)', QFileDialog.getSaveFileName)
        try:
            with open(self.stats['config_file'], 'w+') as f:
                for key, val in self.stats.items():
                    if key == 'channels':
                        f.write(key+'='+channel_str(val)+'\n')
                    else:
                        f.write(key+'='+str(val)+'\n')
            logger.info('DAQ config saved to '+self.stats['config_file'])
        except Exception as e: 
            logger.error('DAQ settings could not be saved to config file.\n'+str(e))

    def load_config(self, file_name='daqconfig.dat'):
        """Load the acquisition settings from the config file."""
        self.stats['config_file'] = file_name if file_name else self.try_browse(file_type='dat (*.dat);;all (*)')
        try:
            with open(self.stats['config_file'], 'r') as f:
                for line in f:
                    if len(line.split('=')) == 2:
                        key, val = line.replace('\n','').split('=') # there should only be one = per line
                        try:
                            self.stats[key] = self.types[key](val)
                        except KeyError as e:
                            logger.warning('Failed to load DAQ default config line: '+line+'\n'+str(e))
            self.set_table() # make sure the updates are displayed
            self.set_n(self.stats['n'])
            self.set_save_dir(self.stats['save_dir'])
            self.set_trace_file(self.stats['trace_file'])
            self.set_graph_file(self.stats['graph_file'])
            self.dc.channels = list(self.stats['channels'].keys())
            logger.info('DAQ config loaded from '+self.stats['config_file'])
        except FileNotFoundError as e: 
            logger.warning('DAQ settings could not find the config file.\n'+str(e))

    def save_trace(self, file_name=''):
        """Save the data currently displayed on the trace to a csv file."""
        file_name = file_name if file_name else self.try_browse(
                'Save File', 'csv (*.csv);;all (*)', QFileDialog.getSaveFileName)
        if file_name:
            # metadata
            header = ', '.join(list(self.stats.keys())) + '\n'
            header += ', '.join(list(map(str, self.stats.values()))[:-1]
                ) + ', ' + channel_str(self.stats['channels']) + '\n'
            # determine which channels are in the plot
            header += 'Time (s)'
            data = []
            for key, d in self.stats['channels'].items():
                if d['plot']:
                    header += ', ' + key # column headings
                    if len(data) == 0: # time (s)
                        data.append(self.lines[int(key[-1])].xData)
                    data.append(self.lines[int(key[-1])].yData) # voltage
            # data converted to the correct type
            out_arr = np.array(data).T
            try:
                np.savetxt(file_name, out_arr, fmt='%s', delimiter=',', header=header)
                logger.info('DAQ trace saved to '+file_name)
            except (PermissionError, FileNotFoundError) as e:
                logger.error('DAQ controller denied permission to save file: \n'+str(e))

    def load_trace(self, file_name=''):
        """Load data for the current trace from a csv file."""
        file_name = file_name if file_name else self.try_browse(file_type='csv(*.csv);;all (*)')
        if file_name:
            head = [[],[],[]] # get metadata
            with open(file_name, 'r') as f:
                for i in range(3):
                    row = f.readline()
                    if row[:2] == '# ':
                        head[i] = row[2:].replace('\n','').split(', ')
            # apply the acquisition settings from the file
            labels = [self.settings.horizontalHeaderItem(i).text() for 
                i in range(self.settings.columnCount())]
            for i in range(len(head[0])):
                try:
                    j = labels.index(head[0][i])
                    self.settings.cellWidget(0,j).setText(head[1][i])
                except ValueError: pass
            self.stats['channels'] = channel_stats(', '.join(head[1][7:]))
            for i in range(8): # whether to plot or not
                ch = self.channels.cellWidget(i,0).text()
                if ch in head[2]:
                    self.channels.cellWidget(i,6).setText('1')
                    self.channels.cellWidget(i,1).setText(self.stats['channels'][ch]['label'])
                else: self.channels.cellWidget(i,6).setText('0')
            self.check_settings()

            # plot the data
            data = np.genfromtxt(file_name, delimiter=',', dtype=float)
            if np.size(data) < 2:
                return 0 # insufficient data to load
            self.update_trace(data.T[1:])

    def save_graph(self, file_name=''):
        """Save the data accumulated from several runs that's displayed in the
        graph into a csv file."""
        file_name = file_name if file_name else self.try_browse(
                'Save File', 'csv (*.csv);;all (*)', QFileDialog.getSaveFileName)
        if file_name:
            self.dc.save(file_name, list(self.stats.keys()), 
                list(map(str, self.stats.values()))[:-1]
                 + [channel_str(self.stats['channels'])])
            logger.info('DAQ graph saved to '+file_name)
        
    def closeEvent(self, event):
        """Before closing, try to save the config settings to file."""
        statstr = "[[" # dictionary of channel names and properties
        for i in range(self.channels.rowCount()):
            statstr += ', '.join([self.channels.cellWidget(i,j).text() 
                    for j in range(self.channels.columnCount())]) + '],['
        self.stats['channels'] = channel_stats(statstr[:-2] + ']')
        # add all channels to stats
        self.save_config(self.stats['config_file'])
        event.accept()
Exemplo n.º 5
0
class BarcodeTable(QGroupBox):
    """ GUI component. Displays a list of barcodes for the currently selected puck.
    """
    def __init__(self, options):
        super(BarcodeTable, self).__init__()

        self._barcodes = []

        self._options = options

        self.setTitle("Barcodes")
        self._init_ui()

    def _init_ui(self):
        # Create record table - lists all the records in the store
        self._table = QTableWidget()

        # Create barcode table - lists all the barcodes in a record
        self._table = QtGui.QTableWidget()
        self._table.setFixedWidth(110)
        self._table.setFixedHeight(600)
        self._table.setColumnCount(1)
        self._table.setRowCount(10)
        self._table.setHorizontalHeaderLabels(['Barcode'])
        self._table.setColumnWidth(0, 100)

        # Clipboard button - copy the selected barcodes to the clipboard
        self._btn_clipboard = QtGui.QPushButton('Copy To Clipboard')
        self._btn_clipboard.setToolTip(
            'Copy barcodes for the selected record to the clipboard')
        self._btn_clipboard.resize(self._btn_clipboard.sizeHint())
        self._btn_clipboard.clicked.connect(self.copy_selected_to_clipboard)
        self._btn_clipboard.setEnabled(False)

        hbox = QHBoxLayout()
        hbox.setSpacing(10)
        hbox.addWidget(self._btn_clipboard)
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addWidget(self._table)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    def populate(self, barcodes):
        """ Called when a new row is selected on the record table. Displays all of the
        barcodes from the selected record in the barcode table. By default, valid barcodes are
        highlighted green, invalid barcodes are highlighted red, and empty slots are grey.
        """
        num_slots = len(barcodes)
        self._table.clearContents()
        self._table.setRowCount(num_slots)

        for index, barcode in enumerate(barcodes):
            # Select appropriate background color
            if barcode == NOT_FOUND_SLOT_SYMBOL:
                color = self._options.col_bad()
            elif barcode == EMPTY_SLOT_SYMBOL:
                color = self._options.col_empty()
            else:
                color = self._options.col_ok()

            color.a = 192

            # Set table item
            barcode = QtGui.QTableWidgetItem(barcode)
            barcode.setBackgroundColor(color.to_qt())
            barcode.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self._table.setItem(index, 0, barcode)

        self._barcodes = barcodes[:]
        for i, barcode in enumerate(self._barcodes):
            if barcode in [NOT_FOUND_SLOT_SYMBOL, EMPTY_SLOT_SYMBOL]:
                self._barcodes[i] = ""

        self._update_button_state()

    def _update_button_state(self):
        if self._barcodes is None or len(self._barcodes) == 0:
            self._btn_clipboard.setEnabled(False)
        else:
            self._btn_clipboard.setEnabled(True)

    def copy_selected_to_clipboard(self):
        """ Called when the copy to clipboard button is pressed. Copies the list/s of
        barcodes for the currently selected records to the clipboard so that the user
        can paste it elsewhere.
        """
        sep = os.linesep
        if self._barcodes:
            pyperclip.copy(sep.join(self._barcodes))
Exemplo n.º 6
0
class Previewer(QMainWindow):
    """Provide a display of a sequence, reminiscent
    of DExTer main view.
    """
    def __init__(self, tr=translate(), precision=4):
        super().__init__()
        self.p = precision + 1  # number of s.f. for floating points
        self.tr = tr
        self.init_UI()
        self.set_sequence()

    def reset_table(self, table, digital=1):
        """Set empty table items in all of the cells of the
        given table. The items are not editable.
        digital -- 1: Set the background colour red
                -- 0: Set the text as ''."""
        for i in range(table.rowCount()):
            for j in range(table.columnCount()):
                item = QTableWidgetItem()
                item.setFlags(item.flags() & ~Qt.ItemIsEditable)
                table.setItem(i, j, item)
                if digital:
                    table.item(i, j).setBackground(Qt.red)
                else:
                    table.item(i, j).setText('')

    def init_UI(self):
        """Create all of the widget objects required"""
        self.centre_widget = QWidget()
        self.tabs = QTabWidget()  # make tabs for each main display
        self.centre_widget.layout = QVBoxLayout()
        self.centre_widget.layout.addWidget(self.tabs)
        self.centre_widget.setLayout(self.centre_widget.layout)
        self.setCentralWidget(self.centre_widget)

        num_e = len(self.tr.seq_dic['Event list array in'])
        num_s = len(self.tr.seq_dic['Experimental sequence cluster in']
                    ['Sequence header top'])
        menubar = self.menuBar()

        # save/load a sequence file
        menubar.clear(
        )  # prevents recreating menubar if init_UI() is called again
        seq_menu = menubar.addMenu('Sequence')
        load = QAction('Load Sequence', self)
        load.triggered.connect(self.load_seq_from_file)
        seq_menu.addAction(load)
        save = QAction('Save Sequence', self)
        save.triggered.connect(self.save_seq_file)
        seq_menu.addAction(save)

        #### tab for previewing sequences ####
        preview_tab = QWidget()
        prv_layout = QVBoxLayout()
        preview_tab.setLayout(prv_layout)
        scroll_widget = QWidget()
        prv_layout.addWidget(scroll_widget)
        prv_vbox = QVBoxLayout()
        scroll_widget.setLayout(prv_vbox)
        self.tabs.addTab(preview_tab, "Sequence")

        # position the widgets on the layout:
        # metadata
        self.routine_name = QLabel('', self)
        self.routine_desc = QLabel('', self)
        for label, name in [[self.routine_name, 'Routine name: '],
                            [self.routine_desc, 'Routine description: ']]:
            layout = QHBoxLayout()
            title = QLabel(name, self)
            title.setFixedWidth(200)
            layout.addWidget(title)
            label.setStyleSheet('border: 1px solid black')
            label.setFixedWidth(400)
            layout.addWidget(label)
            prv_vbox.addLayout(layout)

        # list of event descriptions
        self.e_list = QTableWidget(4, num_e)
        self.e_list.setVerticalHeaderLabels([
            'Event name: ', 'Routine specific event? ', 'Event indices: ',
            'Event path: '
        ])
        self.e_list.setFixedHeight(150)
        self.reset_table(self.e_list, 0)
        prv_vbox.addWidget(self.e_list)

        # event header top
        self.head_top = QTableWidget(14, num_s)
        self.head_top.setVerticalHeaderLabels([
            'Skip Step: ', 'Event name: ', 'Hide event steps: ', 'Event ID: ',
            'Time step name: ', 'Populate multirun: ', 'Time step length: ',
            'Time unit: ', 'D/A trigger: ', 'Trigger this time step? ',
            'Channel: ', 'Analogue voltage (V): ', 'GPIB event name: ',
            'GPIB on/off? '
        ])
        self.head_top.setFixedHeight(450)
        self.reset_table(self.head_top, 0)
        prv_vbox.addWidget(self.head_top)

        # fast digital channels
        fd_head = QLabel('Fast Digital', self)
        prv_vbox.addWidget(fd_head)

        self.fd_chans = QTableWidget(self.tr.nfd, num_s)
        self.fd_chans.setFixedHeight(400)
        self.reset_table(self.fd_chans, 1)
        prv_vbox.addWidget(self.fd_chans)

        # fast analogue channels
        fa_head = QLabel('Fast Analogue', self)
        prv_vbox.addWidget(fa_head)
        self.fa_chans = QTableWidget(self.tr.nfa, num_s * 2)
        self.fa_chans.setFixedHeight(260)
        self.reset_table(self.fa_chans, 0)
        prv_vbox.addWidget(self.fa_chans)

        # event header middle
        self.head_mid = QTableWidget(14, num_s)
        self.head_mid.setVerticalHeaderLabels([
            'Skip Step: ', 'Event name: ', 'Hide event steps: ', 'Event ID: ',
            'Time step name: ', 'Populate multirun: ', 'Time step length: ',
            'Time unit: ', 'D/A trigger: ', 'Trigger this time step? ',
            'Channel: ', 'Analogue voltage (V): ', 'GPIB event name: ',
            'GPIB on/off? '
        ])
        self.head_mid.setFixedHeight(450)
        self.reset_table(self.head_mid, 0)
        prv_vbox.addWidget(self.head_mid)

        # slow digital channels
        sd_head = QLabel('Slow Digital', self)
        prv_vbox.addWidget(sd_head)

        self.sd_chans = QTableWidget(self.tr.nsd, num_s)
        self.sd_chans.setFixedHeight(400)
        self.reset_table(self.sd_chans, 1)
        prv_vbox.addWidget(self.sd_chans)

        # slow analogue channels
        sa_head = QLabel('Slow Analogue', self)
        prv_vbox.addWidget(sa_head)

        self.sa_chans = QTableWidget(self.tr.nsa, num_s * 2)
        self.sa_chans.setFixedHeight(400)
        self.reset_table(self.sa_chans, 0)
        prv_vbox.addWidget(self.sa_chans)

        # place scroll bars if the contents of the window are too large
        scroll = QScrollArea(self)
        scroll.setWidget(scroll_widget)
        scroll.setWidgetResizable(True)
        scroll.setFixedHeight(800)
        prv_layout.addWidget(scroll)

        #### tab for multi-run settings ####
        self.mr = multirun_widget(self.tr)
        self.tabs.addTab(self.mr, "Multirun")

        mr_menu = menubar.addMenu('Multirun')
        mrload = QAction('Load Parameters', self)
        mrload.triggered.connect(self.mr.load_mr_params)
        mr_menu.addAction(mrload)
        mrsave = QAction('Save Parameters', self)
        mrsave.triggered.connect(self.mr.save_mr_params)
        mr_menu.addAction(mrsave)
        mrqueue = QAction('View Queue', self)
        mrqueue.triggered.connect(self.mr.view_mr_queue)
        mr_menu.addAction(mrqueue)

        # choose main window position and dimensions: (xpos,ypos,width,height)
        self.setWindowTitle('Multirun Editor and Sequence Preview')
        self.setWindowIcon(QIcon('docs/previewicon.png'))

    def reset_UI(self):
        """After loading in a new sequence, adjust the UI
        so that the tables have the right number of rows and columns. """
        num_e = len(self.tr.seq_dic['Event list array in'])
        num_s = len(self.tr.seq_dic['Experimental sequence cluster in']
                    ['Sequence header top'])
        for table, rows, cols, dig in [
            [self.e_list, 4, num_e, 0], [self.head_top, 14, num_s, 0],
            [self.fd_chans, self.tr.nfd, num_s, 1],
            [self.fa_chans, self.tr.nfa, num_s * 2, 0],
            [self.head_mid, 14, num_s, 0],
            [self.sd_chans, self.tr.nsd, num_s, 1],
            [self.sa_chans, self.tr.nsa, num_s * 2, 0]
        ]:
            table.setRowCount(rows)
            table.setColumnCount(cols)
            self.reset_table(table, dig)

        self.mr.reset_sequence(self.tr)

    def try_browse(self,
                   title='Select a File',
                   file_type='all (*)',
                   open_func=QFileDialog.getOpenFileName):
        """Open a file dialog and retrieve a file name from the browser.
        title: String to display at the top of the file browser window
        default_path: directory to open first
        file_type: types of files that can be selected
        open_func: the function to use to open the file browser"""
        try:
            if 'PyQt4' in sys.modules:
                file_name = open_func(self, title, '', file_type)
            elif 'PyQt5' in sys.modules:
                file_name, _ = open_func(self, title, '', file_type)
            return file_name
        except OSError:
            return ''  # probably user cancelled

    def load_seq_from_file(self, fname=''):
        """Choose a file name, load the sequence and then show it in the previewer."""
        if not fname: fname = self.try_browse(file_type='XML (*.xml);;all (*)')
        if fname:
            try:
                self.tr.load_xml(fname)
                self.reset_UI()
                self.set_sequence()
            except TypeError as e:
                logger.error("Tried to load invalid sequence")

    def save_seq_file(self, fname=''):
        """Save the current sequence to an xml file."""
        if not fname:
            fname = self.try_browse(title='Choose a file name',
                                    file_type='XML (*.xml);;all (*)',
                                    open_func=QFileDialog.getSaveFileName)
        if fname:
            self.tr.write_to_file(fname)

    def set_sequence(self):
        """Fill the labels with the values from the sequence"""
        seq = self.tr.seq_dic
        self.routine_name.setText(seq['Routine name in'])
        self.routine_desc.setText(seq['Routine description in'])
        ela = seq['Event list array in']  # shorthand
        esc = seq['Experimental sequence cluster in']
        self.fd_chans.setVerticalHeaderLabels(
            map(str.__add__, esc['Fast digital names']['Hardware ID'], [
                ': ' + name if name else ''
                for name in esc['Fast digital names']['Name']
            ]))
        self.fa_chans.setVerticalHeaderLabels(
            map(str.__add__, esc['Fast analogue names']['Hardware ID'], [
                ': ' + name if name else ''
                for name in esc['Fast analogue names']['Name']
            ]))
        self.sd_chans.setVerticalHeaderLabels(
            map(str.__add__, esc['Slow digital names']['Hardware ID'], [
                ': ' + name if name else ''
                for name in esc['Slow digital names']['Name']
            ]))
        self.sa_chans.setVerticalHeaderLabels(
            map(str.__add__, esc['Slow analogue names']['Hardware ID'], [
                ': ' + name if name else ''
                for name in esc['Slow analogue names']['Name']
            ]))
        for i in range(len(ela)):
            self.e_list.item(0, i).setText(ela[i]['Event name'])
            self.e_list.item(1,
                             i).setText(str(ela[i]['Routine specific event?']))
            self.e_list.item(2, i).setText(','.join(
                map(str, ela[i]['Event indices'])))
            self.e_list.item(3, i).setText(ela[i]['Event path'])
        for i in range(len(esc['Sequence header top'])):
            for j, key in enumerate([
                    'Skip Step', 'Event name', 'Hide event steps', 'Event ID',
                    'Time step name', 'Populate multirun', 'Time step length',
                    'Time unit', 'Digital or analogue trigger?',
                    'Trigger this time step?', 'Channel',
                    'Analogue voltage (V)', 'GPIB event name', 'GPIB on/off?'
            ]):
                if key == 'Time step length' or key == 'Analogue voltage (V)':
                    self.head_top.item(j, i).setText(
                        fmt(esc['Sequence header top'][i][key],
                            self.p))  # to 'p' s.f.
                    self.head_mid.item(j, i).setText(
                        fmt(esc['Sequence header middle'][i][key], self.p))
                else:
                    self.head_top.item(j, i).setText(
                        str(esc['Sequence header top'][i][key]))
                    self.head_mid.item(j, i).setText(
                        str(esc['Sequence header middle'][i][key]))
            self.fd_chans.setHorizontalHeaderLabels(
                [h['Time step name'] for h in esc['Sequence header top']])
            for j in range(self.tr.nfd):
                self.fd_chans.item(j, i).setBackground(Qt.green if BOOL(
                    esc['Fast digital channels'][i][j]) else Qt.red)
            self.fa_chans.setHorizontalHeaderLabels([
                h['Time step name'] for h in esc['Sequence header top']
                for j in range(2)
            ])
            for j in range(self.tr.nfa):
                self.fa_chans.item(j, 2 * i).setText(
                    fmt(esc['Fast analogue array'][j]['Voltage'][i], self.p))
                self.fa_chans.item(j, 2 * i + 1).setText('Ramp' if BOOL(
                    esc['Fast analogue array'][j]['Ramp?'][i]) else '')
            self.sd_chans.setHorizontalHeaderLabels(
                [h['Time step name'] for h in esc['Sequence header middle']])
            for j in range(self.tr.nsd):
                self.sd_chans.item(j, i).setBackground(Qt.green if BOOL(
                    esc['Slow digital channels'][i][j]) else Qt.red)
            self.sa_chans.setHorizontalHeaderLabels([
                h['Time step name'] for h in esc['Sequence header middle']
                for j in range(2)
            ])
            for j in range(self.tr.nsa):
                self.sa_chans.item(j, 2 * i).setText(
                    fmt(esc['Slow analogue array'][j]['Voltage'][i], self.p))
                self.sa_chans.item(j, 2 * i + 1).setText('Ramp' if BOOL(
                    esc['Slow analogue array'][j]['Ramp?'][i]) else '')

    def choose_multirun_dir(self):
        """Allow the user to choose the directory where the histogram .csv
        files and the measure .dat file will be saved as part of the multi-run"""
        try:
            dir_path = QFileDialog.getExistingDirectory(
                self, "Select Directory", '')
            self.multirun_save_dir.setText(dir_path)
        except OSError:
            pass  # user cancelled - file not found
Exemplo n.º 7
0
class BarcodeTable(QGroupBox):
    """ GUI component. Displays a list of barcodes for the currently selected puck.
    """
    def __init__(self, options):
        super(BarcodeTable, self).__init__()

        self._barcodes = []

        self._options = options

        self.setTitle("Barcodes")
        self._init_ui()

    def _init_ui(self):
        # Create record table - lists all the records in the store
        self._table = QTableWidget()

        # Create barcode table - lists all the barcodes in a record
        self._table = QtGui.QTableWidget()
        self._table.setFixedWidth(110)
        self._table.setFixedHeight(600)
        self._table.setColumnCount(1)
        self._table.setRowCount(10)
        self._table.setHorizontalHeaderLabels(['Barcode'])
        self._table.setColumnWidth(0, 100)

        # Clipboard button - copy the selected barcodes to the clipboard
        self._btn_clipboard = QtGui.QPushButton('Copy To Clipboard')
        self._btn_clipboard.setToolTip('Copy barcodes for the selected record to the clipboard')
        self._btn_clipboard.resize(self._btn_clipboard.sizeHint())
        self._btn_clipboard.clicked.connect(self.copy_selected_to_clipboard)
        self._btn_clipboard.setEnabled(False)

        hbox = QHBoxLayout()
        hbox.setSpacing(10)
        hbox.addWidget(self._btn_clipboard)
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addWidget(self._table)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    def populate(self, barcodes):
        """ Called when a new row is selected on the record table. Displays all of the
        barcodes from the selected record in the barcode table. By default, valid barcodes are
        highlighted green, invalid barcodes are highlighted red, and empty slots are grey.
        """
        num_slots = len(barcodes)
        self._table.clearContents()
        self._table.setRowCount(num_slots)

        for index, barcode in enumerate(barcodes):
            # Select appropriate background color
            if barcode == NOT_FOUND_SLOT_SYMBOL:
                color = self._options.col_bad()
            elif barcode == EMPTY_SLOT_SYMBOL:
                color = self._options.col_empty()
            else:
                color = self._options.col_ok()

            color.a = 192

            # Set table item
            barcode = QtGui.QTableWidgetItem(barcode)
            barcode.setBackgroundColor(color.to_qt())
            barcode.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
            self._table.setItem(index, 0, barcode)

        self._barcodes = barcodes[:]
        for i, barcode in enumerate(self._barcodes):
            if barcode in [NOT_FOUND_SLOT_SYMBOL, EMPTY_SLOT_SYMBOL]:
                self._barcodes[i] = ""

        self._update_button_state()

    def _update_button_state(self):
        if self._barcodes is None or len(self._barcodes) == 0:
            self._btn_clipboard.setEnabled(False)
        else:
            self._btn_clipboard.setEnabled(True)

    def copy_selected_to_clipboard(self):
        """ Called when the copy to clipboard button is pressed. Copies the list/s of
        barcodes for the currently selected records to the clipboard so that the user
        can paste it elsewhere.
        """
        sep = os.linesep
        if self._barcodes:
            pyperclip.copy(sep.join(self._barcodes))
Exemplo n.º 8
0
class SentenceFillDlg(QDialog, ui_sentencefilldlg.Ui_SentenceFillDlg):
	
	def __init__(self, tableName, rangeValues, parent=None):
		super(SentenceFillDlg, self).__init__(parent)
		self.tableName = tableName
		self.rangeValues = rangeValues
		self.setupUi(self)
		self.initUI()
		
	def initUI(self):
		fontLabel = QFont('SansSerif', 14)
		
		self.setMinimumSize(1200, 600)
		self.resize(1200, 600)
		
		self.sentenceToFillTable = QTableWidget(5, 1, self)
		self.sentenceToFillTable.setGeometry(QRect(70, 60, 800, 400))
		
		self.sentenceToFillTable.setColumnCount(1)
		
		item = QTableWidgetItem()
		item.setText('Sentences To Match')
		font = QFont()
		font.setBold(True)
		font.setWeight(75)
		item.setFont(font)
		self.sentenceToFillTable.setHorizontalHeaderItem(0, item)
		self.sentenceToFillTable.resizeColumnsToContents()
		self.sentenceToFillTable.horizontalHeader().setStretchLastSection(True)
		self.sentenceToFillTable.verticalHeader().setStretchLastSection(True)
		
		self.sentenceList = []
		self.getSentencesFromDatabase()
		
		# split the sentences into chunks of 5
		# Map to hold the anwers
		self.rightWordList = {}
		
		self.iteration = 0
		self.sentenceMasterList = []
		self.sentenceSlaveList = []
		
		if len(self.sentenceList) > 0:			
			for sentence in self.sentenceList:
				if len(self.sentenceSlaveList) < 5:
					self.sentenceSlaveList.append(sentence)
				else:
					self.sentenceMasterList.append(self.sentenceSlaveList)
					self.sentenceSlaveList = []
					self.sentenceSlaveList.append(sentence)
					
			if len(self.sentenceSlaveList) <= 5:
				self.sentenceMasterList.append(self.sentenceSlaveList)
				
			self.maxIteration = len(self.sentenceMasterList)
		
		
		## set the row height
		self.tableHeightSize = 0
		## Only if there is atleast one sentence fetched from the database
		if len(self.sentenceList) > 0:			
			for index in range(0, 5):
				if len(self.sentenceList[index]) < 150:
					self.sentenceToFillTable.verticalHeader().resizeSection(index, 80)
					self.tableHeightSize = self.tableHeightSize + 90
				elif len(self.sentenceList[index]) < 200:
					self.sentenceToFillTable.verticalHeader().resizeSection(index, 100)
					self.tableHeightSize = self.tableHeightSize + 100
				else:
					self.sentenceToFillTable.verticalHeader().resizeSection(index, 120)
					self.tableHeightSize = self.tableHeightSize + 120
			
				
			# split words from databse into chunks of 5
		
			self.refIteration = 0
			self.refWordMasterList = []
			self.refWordSlaveList = []
			for wordList in self.wordMap.values():
				if len(self.refWordSlaveList) < 5:
					self.refWordSlaveList.append(wordList)
				else:
					self.refWordMasterList.append(self.refWordSlaveList)
					self.refWordSlaveList = []
					self.refWordSlaveList.append(wordList)
			
			if len(self.refWordSlaveList) <= 5:
				self.refWordMasterList.append(self.refWordSlaveList)
				
			self.refMaxIteration = len(self.refWordMasterList)
		
			self.insertSentencesInTable()
		
		self.connect(self.sentenceToFillTable, SIGNAL("cellClicked(int, int)"), self.itemSelect)
		
		# create next, submit and close buttons
		self.nextButton = QPushButton('Next', self)
		self.nextButton.move(170, 520)
		self.nextButton.setEnabled(False)  
		
		self.submitButton = QPushButton('Submit', self)
		self.submitButton.move(380, 520)
		
		self.closeButton = QPushButton('Close', self)
		self.closeButton.move(600, 520)
		
		self.connect(self.closeButton, SIGNAL('clicked()'), self.closeClicked)
		
		self.connect(self.submitButton, SIGNAL('clicked()'), self.onSubmitClicked)
		
		self.connect(self.nextButton, SIGNAL('clicked()'), self.onNextClicked)
		
		self.setWindowTitle('Sentence Fill')
		
		#self.setGeometry(10, 80, 1100, 750)


	def getSentencesFromDatabase(self):
		self.twoTypeList = data.Data().getSentencesFromDatabase(self.tableName, self.rangeValues)
		if len(self.twoTypeList) == 2:
			self.sentenceList = self.twoTypeList[0]
			self.wordMap = self.twoTypeList[1]

	def closeClicked(self):
		reply = QMessageBox.question(self,
					"Fill the Sentences",
					"You want to close the application",
					QMessageBox.Yes | QMessageBox.No)
		if reply == QMessageBox.Yes:
			self.reject()
			
	def onNextClicked(self):
		if not self.iteration < self.maxIteration:
			QMessageBox.information(self,
					"Fill the Sentences",
					"All Sentences are matched",
					QMessageBox.Ok)
		else:
			self.sentenceToFillTable.clearContents()
			## Fetch the next records
			self.refIteration = self.refIteration + 1
			self.rightWordList = {}
			## clear the contents of the combo box
			self.wordListComboBox.clear()
			self.insertSentencesInTable()
	
	# validate whether the user has matched the blanks with a value during next and submit button clicks
	def onNextSubmitValidation(self):
		self.textNotFilled = False
		for row in range(0, len(self.currentSentenceListInTable)):
			if self.sentenceToFillTable.item(row, 0).text() == self.currentSentenceListInTable[row]:
				self.textNotFilled = True
		if self.textNotFilled:
			QMessageBox.information(self,
						"Fill the sentences",
						"Match all the sentences in the table",
						QMessageBox.Ok)
			self.nextButton.setEnabled(False)
			
			
	def onSubmitClicked(self):
		self.wordsMatched = True
		self.allWordsMatched = True
		brushRed = QBrush(QColor(255,0,0))
		brushRed.setStyle(Qt.SolidPattern)
		
		brushGreen = QBrush(QColor(0,255,0))
		brushGreen.setStyle(Qt.SolidPattern)
		
		# validate whether the user all matched the blanks with a value
		#self.onNextSubmitValidation()
		textNotFilled = False
		for row in range(0, len(self.currentSentenceListInTable)):
			if self.sentenceToFillTable.item(row, 0).text() == self.currentSentenceListInTable[row]:
				textNotFilled = True
		if textNotFilled:
			QMessageBox.information(self,
						"Fill the sentences",
						"Match all the sentences in the table",
						QMessageBox.Ok)
		else:
			splittedSentence = []
			foundWordInSentence = ""
			self.rightWordListCopy = self.rightWordList
			for row in range(0, len(self.currentSentenceListInTable)):
				sentenceFilled = str(self.sentenceToFillTable.item(row, 0).text())
				newSplittedSentence = [word.strip() for word in sentenceFilled.split()]
				splittedSentence = []
				for word in newSplittedSentence:
					match = re.search(r'\w+', word)
					if match:
						splittedSentence.append(str(match.group()))
				
				wordList = self.rightWordListCopy[row]
				
				if len(wordList) > 1:
					firstWord = wordList[0].strip()
					secondWord = wordList[1].strip()
					if ' ' in firstWord:
						for word in firstWord.split():
							if word not in splittedSentence:
								self.wordsMatched = False
					else:
						if firstWord not in splittedSentence:
							self.wordsMatched = False
					
					if self.wordsMatched: ## check is valid only if the first word is matched
						if ' ' in secondWord:
							for word in secondWord.split():
								if word not in splittedSentence:
									self.wordsMatched = False
						else:
							if secondWord not in splittedSentence:
								self.wordsMatched = False
				elif len(wordList) == 1:
					word = wordList[0].strip()
					if word not in splittedSentence:
						self.wordsMatched = False
				
				if self.wordsMatched:
					self.sentenceToFillTable.item(row, 0).setBackground(brushGreen)
				else:
					self.sentenceToFillTable.item(row, 0).setBackground(brushRed)
					self.allWordsMatched = False
				
				self.wordsMatched = True
					
			if self.allWordsMatched:
				self.nextButton.setEnabled(True)
				QMessageBox.information(self,
								"Fill the sentences",
								"All sentences are matched",
								QMessageBox.Ok)

	def insertSentencesInTable(self):
		if self.iteration < self.maxIteration:
			cellList = []
			self.col = 0
			self.currentSentenceListInTable = self.sentenceMasterList[self.iteration]
			for sentence in self.currentSentenceListInTable:
				item = QTableWidgetItem(QString(sentence))
				item.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable |
								Qt.ItemIsEnabled))
				cellList.append(item)
			cellList.reverse()
			
			for index in range(0, len(self.currentSentenceListInTable)):
				self.sentenceToFillTable.setItem(index, self.col, cellList.pop())
				
			self.sentenceToFillTable.setRowCount(len(self.sentenceMasterList[self.iteration]))
			
			self.sentenceToFillTable.setFixedHeight(self.tableHeightSize)
			
			# increment the count for the next button click
			self.iteration = self.iteration + 1
			
	def createComboBox(self, rowIndex):
		self.xAxis = 1000
		self.yAxis = 80
		
		if self.refIteration < self.refMaxIteration:
			wordListWithFiveArrays = self.refWordMasterList[self.refIteration]
			requiredWordList = wordListWithFiveArrays[rowIndex]
			
			## Create a combo box
			self.wordListComboBox = QComboBox(self)
			self.wordListComboBox.setGeometry(900, 80, 100, 20)
			self.connect(self.wordListComboBox, SIGNAL("currentIndexChanged(int)"),
							self.wordSelected)
			self.wordListComboBox.show()
			
			## save the right word
			if len(requiredWordList) > 0:
				if rowIndex not in self.rightWordList.keys():
					if len(requiredWordList) > 0:
						wordList = requiredWordList[0].split('&')
						self.rightWordList[rowIndex] = wordList

			## insert words in the combo box
			random.shuffle(requiredWordList)
			random.shuffle(requiredWordList)
			
			self.wordListComboBox.addItem('         ')
			self.wordListComboBox.addItems(requiredWordList)
	
	def wordSelected(self):
		## Avoid the blank option
		if len(str(self.wordListComboBox.currentText()).strip()) > 0:
			sentence = self.currentSentenceListInTable[self.selectedSentenceIndex]
			splittedText = sentence.split('-')
			if len(splittedText) == 2:
				item = QTableWidgetItem(QString(splittedText[0] +
						str(self.wordListComboBox.currentText()) + ' ' + splittedText[1].strip()))
				item.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable | 
								Qt.ItemIsEnabled))
				self.sentenceToFillTable.setItem(self.selectedSentenceIndex, 0, item)
			elif len(splittedText) > 2:
				wordsFromCombobox = str(self.wordListComboBox.currentText()).split('&')
				item = QTableWidgetItem(QString(splittedText[0] + wordsFromCombobox[0]
							+ splittedText[1] + wordsFromCombobox[1] + ' ' + splittedText[2].strip()))
				item.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable | 
							Qt.ItemIsEnabled))
				self.sentenceToFillTable.setItem(self.selectedSentenceIndex, 0, item)
	
	def createIndexForWords(self):
		self.indexForWords = {}
		self.nextIndex = 0
		for word in self.refWordListForIndex:
			if self.nextIndex == 5:
				break
			self.indexForWords[word] = self.nextIndex
			self.nextIndex = self.nextIndex + 1
	
	def itemSelect(self,rowIndex):
		self.createComboBox(rowIndex)
		self.selectedSentenceIndex = rowIndex
Exemplo n.º 9
0
class ScanRecordTable(QGroupBox):
    """ GUI component. Displays a list of previous scan results. Selecting a scan causes
    details of the scan to appear in other GUI components (list of barcodes in the barcode
    table and image of the puck in the image frame).
    """
    COLUMNS = ['Date', 'Time', 'Plate Type', 'Valid', 'Invalid', 'Empty']

    def __init__(self, barcode_table, image_frame, options):
        super(ScanRecordTable, self).__init__()

        # Read the store from file
        self._store = Store(options.store_directory.value(), options)
        self._options = options

        self._barcodeTable = barcode_table
        self._imageFrame = image_frame

        self.setTitle("Scan Records")
        self._init_ui()

        self._load_store_records()

    def _init_ui(self):
        # Create record table - lists all the records in the store
        self._table = QTableWidget()
        self._table.setFixedWidth(440)
        self._table.setFixedHeight(600)
        self._table.setColumnCount(len(self.COLUMNS))
        self._table.setHorizontalHeaderLabels(self.COLUMNS)
        self._table.setColumnWidth(0, 70)
        self._table.setColumnWidth(1, 55)
        self._table.setColumnWidth(2, 85)
        self._table.setColumnWidth(3, 60)
        self._table.setColumnWidth(4, 60)
        self._table.setColumnWidth(5, 60)
        self._table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
        self._table.cellPressed.connect(self._record_selected)

        # Delete button - deletes selected records
        btn_delete = QtGui.QPushButton('Delete')
        btn_delete.setToolTip('Delete selected scan/s')
        btn_delete.resize(btn_delete.sizeHint())
        btn_delete.clicked.connect(self._delete_selected_records)

        hbox = QHBoxLayout()
        hbox.setSpacing(10)
        hbox.addWidget(btn_delete)
        hbox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addWidget(self._table)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    def add_record(self, plate, image):
        """ Add a new scan record to the store and display it. """
        self._store.add_record(plate, image)
        self._load_store_records()

    def add_record_frame(self, plate, image):
        """ Add a new scan frame - creates a new record if its a new puck, else merges with previous record"""
        self._store.merge_record(plate, image)
        self._load_store_records()
        if self._options.scan_clipboard.value():
            self._barcodeTable.copy_selected_to_clipboard()

    def _load_store_records(self):
        """ Populate the record table with all of the records in the store.
        """
        self._table.clearContents()
        self._table.setRowCount(self._store.size())

        for n, record in enumerate(self._store.records):
            items = [record.date, record.time, record.plate_type, record.num_valid_barcodes,
                     record.num_unread_slots, record.num_empty_slots]

            if (record.num_valid_barcodes + record.num_empty_slots) == record.num_slots:
                color = self._options.col_ok()
            else:
                color = self._options.col_bad()

            color.a = 192
            for m, item in enumerate(items):
                new_item = QtGui.QTableWidgetItem(str(item))
                new_item.setBackgroundColor(color.to_qt())
                new_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
                self._table.setItem(n, m, new_item)

        # Display the first (most recent) record
        self._table.setCurrentCell(0, 0)
        self._record_selected()

    def _record_selected(self):
        """ Called when a row is selected, causes details of the selected record to be
        displayed (list of barcodes in the barcode table and image of the scan in the
        image frame).
        """
        try:
            row = self._table.selectionModel().selectedRows()[0].row()
            record = self._store.get_record(row)
            self._barcodeTable.populate(record.barcodes)
            marked_image = record.marked_image(self._options)
            self._imageFrame.display_puck_image(marked_image)
        except IndexError:
            pass
            self._barcodeTable.populate([])
            self._imageFrame.clear_frame()

    def _delete_selected_records(self):
        """ Called when the 'Delete' button is pressed. Deletes all of the selected records
        (and the associated images) from the store and from disk. Asks for user confirmation.
        """
        # Display a confirmation dialog to check that user wants to proceed with deletion
        quit_msg = "This operation cannot be undone.\nAre you sure you want to delete these record/s?"
        reply = QtGui.QMessageBox.warning(self, 'Confirm Delete',
                         quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

        # If yes, find the appropriate records and delete them
        if reply == QtGui.QMessageBox.Yes:
            rows = self._table.selectionModel().selectedRows()
            records_to_delete = []
            for row in rows:
                index = row.row()
                record = self._store.get_record(index)
                records_to_delete.append(record)

            self._store.delete_records(records_to_delete)
            self._load_store_records()