Пример #1
0
class Lg_addFiles(QDialog, Ui_addFile, object):
    closeGUI_signal = pyqtSignal()
    inputSig = pyqtSignal(list, list, bool, str)
    inputContentSig = pyqtSignal(str, str)
    exception_signal = pyqtSignal(str)
    progressDiologSig = pyqtSignal(int)
    fastaDownloadFinishedSig = pyqtSignal(str)
    inputFasSig = pyqtSignal(str, str)

    def __init__(self, exportPath=None, parent=None):
        super(Lg_addFiles, self).__init__(parent)
        self.factory = Factory()
        self.parent = parent
        self.thisPath = self.factory.thisPath
        self.exportPath = exportPath
        # 保存设置
        self.addFiles_settings = QSettings(
            self.thisPath +
            '/settings/addFiles_settings.ini',
            QSettings.IniFormat)
        self.closeGUI_signal.connect(self.close)
        self.progressDiologSig.connect(self.runProgressDialog)
        # self.close() 不能在信号槽里面
        self.fastaDownloadFinishedSig.connect(self.parent.setTreeViewFocus)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        self.setupUi(self)
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.interrupt = False
        self.label_3.clicked.connect(lambda : QDesktopServices.openUrl(QUrl(
            "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#4-3-1-1-Brief-example")))

    def fetSeqFromNCBI(self, id_array):
        batch_size = 20
        count = len(id_array)
        download_contents = ""
        for start in range(0, count, batch_size):
            if self.interrupt:
                return
            end = min(count, start + batch_size)
            print("Going to download record %i to %i" % (start + 1, end))
            if (start + batch_size) > count:
                batch_size = count - start
            Entrez.email = self.email if self.email else "*****@*****.**"
            fetch_handle = Entrez.efetch(db="nucleotide", rettype=self.rettype, retmode="text",
                                         retstart=start, retmax=batch_size, id=id_array)
            download_contents += fetch_handle.read()
            self.progressDiologSig.emit(end * 100 / count)
        if self.rettype == "gb":
            self.inputContentSig.emit(
                download_contents, self.outputPath)
        else:
            with open(self.outputPath + os.sep + self.fasta_file_name, "w", encoding="utf-8") as f:
                f.write(download_contents)
            self.fastaDownloadFinishedSig.emit(self.outputPath)
        # result_handle = Entrez.efetch(
        #     db="nucleotide", rettype="gb",  id=id_array, retmode="text")
        # # with open(self.exportPath + os.sep + "new.gb", "w", encoding="utf-8") as f2:
        # #     f2.write(result_handle.read())
        # self.inputContentSig.emit(
        #     result_handle.read(), [])

    @pyqtSlot()
    def on_pushButton_clicked(self):
        self.outputPath = self.fetchOutputPath()
        if self.parent.isWorkFolder(self.outputPath, mode="gb"):
            files = QFileDialog.getOpenFileNames(
                self, "Input GenBank Files", filter="GenBank and Fasta Format (*.gb *.gbk *.gbf *.gp *.gbff *.fas *.fasta);;")
            if files[0]:
                list_gbs = []
                list_fas = []
                for i in files[0]:
                    if os.path.splitext(i)[1].upper() in [".GB", ".GBK", ".GP", ".GBF", ".GBFF"]:
                        list_gbs.append(i)
                    elif os.path.splitext(i)[1].upper() in [".FAS", ".FASTA"]:
                        list_fas.append(i)
                if list_gbs:
                    self.inputSig.emit(list_gbs, [], False, self.outputPath)
                if list_fas:
                    fasContent = ""
                    for i in list_fas:
                        with open(i, encoding="utf-8", errors='ignore') as f:
                            fasContent += f.read() + "\n"
                    self.inputFasSig.emit(fasContent, self.outputPath)
                self.close()
                self.deleteLater()
                del self
        else:
            files = QFileDialog.getOpenFileNames(
                self, "Input Files",
                filter="Supported Format (*.docx *.doc *.odt *.docm *.dotx *.dotm *.dot "
                       "*.fas *.fasta *.phy *.phylip *.nex *.nxs *.nexus);;")
            if files[0]:
                self.inputSig.emit([], files[0], False, self.outputPath)
                self.close()
                self.deleteLater()
                del self

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        # download
        self.interrupt = False
        text_content = self.plainTextEdit.toPlainText()
        if text_content:
            self.outputPath = self.fetchOutputPath()
            self.rettype = "gb" if self.parent.isWorkFolder(self.outputPath, mode="gb") else "fasta"
            if self.rettype == "fasta":
                name, ok = QInputDialog.getText(
                    self, 'Set output file name', 'Output file name:', text="sequence.fas")
                if ok:
                    self.fasta_file_name = name + ".fas" if "." not in name else name
                    if os.path.exists(self.outputPath + os.sep + self.fasta_file_name):
                        reply = QMessageBox.question(
                            self,
                            "Concatenate sequence",
                            "<p style='line-height:25px; height:25px'>The file exists, replace it?</p>",
                            QMessageBox.Yes,
                            QMessageBox.Cancel)
                        if reply == QMessageBox.Cancel:
                            return
                else:
                    QMessageBox.information(
                        self,
                        "Information",
                        "<p style='line-height:25px; height:25px'>Download canceled!</p>")
                    return
            self.downloadState("start")
            self.progressDialog = self.factory.myProgressDialog(
                "Please Wait", "Downloading...", parent=self)
            self.progressDialog.show()
            self.id_array = re.split(r"\s|,", text_content)
            while "" in self.id_array:
                self.id_array.remove("")
            self.email = self.lineEdit.text()
            self.worker_download = WorkThread(self.run_command, parent=self)
            self.progressDialog.canceled.connect(lambda: [setattr(self, "interrupt", True),
                                                          self.worker_download.stopWork(),
                                                          self.progressDialog.close(),
                                                          self.downloadState("stop")])
            self.worker_download.start()
        else:
            QMessageBox.information(
                self,
                "Information",
                "<p style='line-height:25px; height:25px'>Please input ID(s) first</p>")

    @pyqtSlot()
    def on_pushButton_8_clicked(self):
        #search in NCBI
        self.close()
        self.parent.on_SerhNCBI_triggered()

    @pyqtSlot()
    def on_toolButton_clicked(self):
        info = '''To make use of NCBI's E-utilities, NCBI requires you to specify your email address with each request.<br> 
In case of excessive usage of the E-utilities, NCBI will attempt to contact a user at the email address provided before blocking access to the E-utilities.'''
        QMessageBox.information(
            self,
            "Information",
            "<p style='line-height:25px; height:25px'>%s</p>"%info)

    def run_command(self):
        try:
            self.fetSeqFromNCBI(self.id_array)
            self.closeGUI_signal.emit()
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)
            # self.popupException(self.exceptionInfo)  # 激发这个信号

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]')
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()
        self.pushButton_2.setEnabled(True)
        self.pushButton_2.setStyleSheet(self.qss_file)
        self.pushButton_2.setText("Start")

    def guiSave(self):
        # Save geometry
        self.addFiles_settings.setValue('size', self.size())
        # self.addFiles_settings.setValue('pos', self.pos())

        # for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            # if isinstance(obj, QTextBrowser):
            #     # save combobox selection to registry
            #     htmlText = obj.toHtml()
            #     self.addFiles_settings.setValue(name, htmlText)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.addFiles_settings.value('size', QSize(500, 500)))
        # self.move(self.addFiles_settings.value('pos', QPoint(875, 254)))
        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox":
                    allItems = self.factory.fetchAllWorkFolders(self.exportPath)
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        if self.parent.isWorkFolder(i, mode="gb"):
                            text = "\"%s\" in GenBank_File (GenBank format)"%os.path.basename(i)
                        else:
                            text = "\"%s\" in Other_File" % os.path.basename(i)
                        item = QStandardItem(text)
                        item.setToolTip(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    if self.parent.isWorkFolder(self.exportPath, mode="gb"):
                        obj.setCurrentText("\"%s\" in GenBank_File (GenBank format)"%os.path.basename(self.exportPath))
                    else:
                        obj.setCurrentText("\"%s\" in Other_File" % os.path.basename(self.exportPath))

    def closeEvent(self, event):
        self.interrupt = True
        if hasattr(self, "worker_download"):
            self.worker_download.stopWork()
        if hasattr(self, "progressDialog"):
            self.progressDialog.close()
        self.guiSave()

    def runProgressDialog(self, num):
        oldValue = self.progressDialog.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressDialog.setProperty("value", done_int)
            QCoreApplication.processEvents()
            if done_int == 100:
                self.progressDialog.close()

    def fetchOutputPath(self):
        index = self.comboBox.currentIndex()
        return self.comboBox.itemData(index, role=Qt.ToolTipRole)

    def downloadState(self, state):
        if state == "start":
            self.pushButton_2.setEnabled(False)  # 使之失效
            self.pushButton_2.setStyleSheet(
                'QPushButton {color: red; background-color: rgb(219, 217, 217)}')
            self.pushButton_2.setText("Downloading...")
        elif state == "stop":
            self.pushButton_2.setText("Download")
            self.pushButton_2.setStyleSheet(self.qss_file)
            self.pushButton_2.setEnabled(True)
Пример #2
0
class ConvertFMT(QDialog, Ui_ConverFMT, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    unalignedSig = pyqtSignal(list)

    def __init__(self,
                 workPath=None,
                 focusSig=None,
                 autoFiles=None,
                 parent=None):
        super(ConvertFMT, self).__init__(parent)
        self.parent = parent
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig
        self.autoFiles = autoFiles
        self.setupUi(self)
        # 保存设置
        self.convertFmt_settings = QSettings(
            self.thisPath + '/settings/convertFmt_settings.ini',
            QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.convertFmt_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.comboBox_4.installEventFilter(self)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  #自动识别可用的输入
        self.unalignedSig.connect(self.popupUnaligns)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-8-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        AllItems = self.comboBox_4.fetchListsText()
        if AllItems:
            self.dict_args = {}
            self.dict_args["exception_signal"] = self.exception_signal
            self.dict_args["unaligned_signal"] = self.unalignedSig
            self.dict_args["progressSig"] = self.progressSig
            self.dict_args["files"] = AllItems
            self.dict_args["workPath"] = self.workPath
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.dict_args["export_path"] = self.factory.creat_dirs(
                self.workPath + os.sep + "convertFmt_results" + os.sep +
                self.output_dir_name)
            self.dict_args["export_phylip"] = self.checkBox.isChecked()
            self.dict_args["export_nex"] = self.checkBox_2.isChecked()
            self.dict_args["export_nexi"] = self.checkBox_3.isChecked()
            self.dict_args["export_axt"] = self.checkBox_13.isChecked()
            self.dict_args["export_paml"] = self.checkBox_5.isChecked()
            self.dict_args["export_fas"] = self.checkBox_12.isChecked()
            self.dict_args["export_stat"] = self.checkBox_9.isChecked()
            if True not in list(self.dict_args.values()):
                QMessageBox.critical(
                    self, "Convert Sequence Format",
                    "<p style='line-height:25px; height:25px'>Please select output format(s) first!</p>"
                )
                self.checkBox.setChecked(True)
                return
            ok = self.factory.remove_dir(self.dict_args["export_path"],
                                         parent=self)
            if not ok:
                #提醒是否删除旧结果,如果用户取消,就不执行
                return
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.critical(
                self, "Convert Sequence Format",
                "<p style='line-height:25px; height:25px'>Please input files first!</p>"
            )

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        open files
        """
        files = QFileDialog.getOpenFileNames(
            self,
            "Input Files",
            filter=
            "Supported Format(*.fas *.fasta *.phy *.phylip *.nex *.nxs *.nexus);;"
        )
        if files[0]:
            self.input(files[0])

    def run_command(self):
        try:
            # 先清空文件夹
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start",
                self.dict_args["export_path"], self.qss_file, self
            ])
            convertFmt = Convertfmt(**self.dict_args)
            convertFmt.exec_()
            if convertFmt.error_message:
                self.exception_signal.emit(convertFmt.error_message)  # 激发这个信号
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.dict_args["export_path"], self.qss_file, self
                ])
            elif convertFmt.unaligns:
                self.unalignedSig.emit(convertFmt.unaligns)
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.dict_args["export_path"], self.qss_file, self
                ])
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop",
                    self.dict_args["export_path"], self.qss_file, self
                ])
            self.focusSig.emit(self.dict_args["export_path"])
            time_end = datetime.datetime.now()
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), str(time_end - time_start))
            with open(self.dict_args["export_path"] + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    "If you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n\n"
                    + self.time_used_des)
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["export_path"], self.qss_file, self
            ])

    def guiSave(self):
        # Save geometry
        self.convertFmt_settings.setValue('size', self.size())
        # self.convertFmt_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.convertFmt_settings.setValue(name, state)
            if isinstance(obj, QRadioButton):
                state = obj.isChecked()
                self.convertFmt_settings.setValue(name, state)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.convertFmt_settings.value('size', QSize(500, 500)))
        # self.move(self.convertFmt_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if self.autoFiles:
                    self.input(self.autoFiles)
                else:
                    self.input([])
            if isinstance(obj, QCheckBox):
                value = self.convertFmt_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QRadioButton):
                value = self.convertFmt_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox

    def input(self, list_inputs):
        self.comboBox_4.refreshInputs(list_inputs)

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.guiSave()

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                files = [
                    i for i in files if os.path.splitext(i)[1].upper() in [
                        ".FAS", ".FASTA", ".PHY", ".PHYLIP", ".NEX", ".NXS",
                        ".NEXUS"
                    ]
                ]
                self.input(files)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        return super(ConvertFMT, self).eventFilter(obj, event)  # 0

    def popupUnaligns(self, message):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Warning)
        msg.setText(
            "<p style='line-height:25px; height:25px'>Unaligned sequences found, see details</p>"
        )
        msg.setWindowTitle("Warning")
        msg.setDetailedText("Unaligned sequences: " + ",".join(message))
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def popupAutoDec(self):
        popupUI = self.factory.popUpAutoDetect("format conversion",
                                               self.workPath, self)
        if not popupUI:
            QMessageBox.warning(
                self, "Warning",
                "<p style='line-height:25px; height:25px'>No available file detected!</p>"
            )
            return
        popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(autoInputs)
Пример #3
0
class ModelFinder(QDialog, Ui_ModelFinder, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    logGuiSig = pyqtSignal(str)
    search_algorithm = pyqtSignal(str)  # 定义所有类都可以使用的信号
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    iq_tree_exception = pyqtSignal(str)  # 定义所有类都可以使用的信号
    # 用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)
    # 用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)
    ##弹出识别输入文件的信号
    auto_popSig = pyqtSignal(QDialog)

    def __init__(
            self,
            autoMFPath=None,
            partFile=None,
            workPath=None,
            focusSig=None,
            IQ_exe=None,
            workflow=False,
            parent=None):
        super(ModelFinder, self).__init__(parent)
        self.parent = parent
        self.function_name = "ModelFinder"
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig if focusSig else pyqtSignal(
            str)  # 为了方便workflow
        self.workflow = workflow
        self.modelfinder_exe = '"' + IQ_exe + '"'
        self.autoMFPath = autoMFPath[0] if type(autoMFPath) == list else autoMFPath
        self.partFile = partFile
        self.interrupt = False
        self.setupUi(self)
        # 保存设置
        if not workflow:
            self.modelfinder_settings = QSettings(
                self.thisPath + '/settings/modelfinder_settings.ini', QSettings.IniFormat)
        else:
            self.modelfinder_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat)
            self.modelfinder_settings.beginGroup("Workflow")
            self.modelfinder_settings.beginGroup("temporary")
            self.modelfinder_settings.beginGroup('ModelFinder')
        # File only, no fallback to registry or or.
        self.modelfinder_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.iq_tree_exception.connect(self.popup_IQTREE_exception)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.logGuiSig.connect(self.addText2Log)
        self.comboBox_4.installEventFilter(self)
        self.log_gui = self.gui4Log()
        self.lineEdit.installEventFilter(self)
        self.lineEdit.autoDetectSig.connect(self.popupAutoDec)
        self.lineEdit_2.installEventFilter(self)
        self.lineEdit_3.installEventFilter(self)
        self.comboBox_3.activated[str].connect(self.controlCodonTable)
        self.comboBox_5.activated[str].connect(self.ctrl_ratehet)
        self.ctrl_ratehet(self.comboBox_5.currentText())
        self.lineEdit_2.setLineEditNoChange(True)
        self.lineEdit_3.setLineEditNoChange(True)
        self.lineEdit.deleteFile.clicked.connect(
            self.clear_lineEdit)  # 删除了内容,也要把tooltip删掉
        self.lineEdit_2.deleteFile.clicked.connect(
            self.clear_lineEdit)  # 删除了内容,也要把tooltip删掉
        self.lineEdit_3.deleteFile.clicked.connect(
            self.clear_lineEdit)  # 删除了内容,也要把tooltip删掉
        # 初始化codon table的选择
        self.controlCodonTable(self.comboBox_3.currentText())
        self.checkBox.stateChanged.connect(self.switchPart)
        self.switchPart(self.checkBox.isChecked())
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        action = QAction(QIcon(":/picture/resourses/terminal-512.png"), "View | Edit command", menu,
                         triggered=self.showCMD)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "", menu)
        self.work_action.triggered.connect(lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"), "Output Dir: ", menu)
        self.dir_action.triggered.connect(lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(action)
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True, parent=self)  # 初始化一下
        ## brief demo
        self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(
            "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-9-1-Brief-example")))
        ##自动弹出识别文件窗口
        self.auto_popSig.connect(self.popupAutoDecSub)

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        try:
            self.command = self.getCMD()
            if self.command:
                self.MF_popen = self.factory.init_popen(self.command)
                self.factory.emitCommands(self.logGuiSig, self.command)
                self.worker = WorkThread(self.run_command, parent=self)
                self.worker.start()
        except:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        alignment file
        """
        fileName = QFileDialog.getOpenFileName(
            self, "Input alignment file", filter="Phylip Format(*.phy *.phylip);;Fasta Format(*.fas *.fasta);;Nexus Format(*.nex *.nexus *.nxs);;Clustal Format(*.aln)")
        if fileName[0]:
            base = os.path.basename(fileName[0])
            self.lineEdit.setText(base)
            self.lineEdit.setToolTip(fileName[0])

    @pyqtSlot()
    def on_pushButton_4_clicked(self):
        """
        tree file
        """
        fileName = QFileDialog.getOpenFileName(
            self, "Input tree file", filter="Newick Format(*.nwk *.newick);;")
        if fileName[0]:
            base = os.path.basename(fileName[0])
            self.lineEdit_2.setText(base)
            self.lineEdit_2.setToolTip(fileName[0])

    @pyqtSlot()
    def on_pushButton_22_clicked(self):
        """
        partition file
        """
        fileName = QFileDialog.getOpenFileName(
            self, "Input partition file", filter="All(*);;")
        if fileName[0]:
            base = os.path.basename(fileName[0])
            self.lineEdit_3.setText(base)
            self.lineEdit_3.setToolTip(fileName[0])

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        show log
        """
        self.log_gui.show()

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        """
        Stop
        """
        if self.isRunning():
            if not self.workflow:
                reply = QMessageBox.question(
                    self,
                    "Confirmation",
                    "<p style='line-height:25px; height:25px'>ModelFinder is still running, terminate it?</p>",
                    QMessageBox.Yes,
                QMessageBox.Cancel)
            else:
                reply = QMessageBox.Yes
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    if platform.system().lower() in ["windows", "darwin"]:
                        self.MF_popen.kill()
                    else:
                        os.killpg(os.getpgid(self.MF_popen.pid), signal.SIGTERM)
                    self.MF_popen = None
                    self.interrupt = True
                except:
                    self.MF_popen = None
                    self.interrupt = True
                if not self.workflow:
                    QMessageBox.information(
                        self,
                        "ModelFinder",
                        "<p style='line-height:25px; height:25px'>Program has been terminated!</p>")
                self.startButtonStatusSig.emit(
                    [
                        self.pushButton,
                        self.progressBar,
                        "except",
                        self.exportPath,
                        self.qss_file,
                        self])

    def run_command(self):
        try:
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit(
                [
                    self.pushButton,
                    self.progressBar,
                    "start",
                    self.exportPath,
                    self.qss_file,
                    self])
            self.run_MF()
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (str(time_start), str(time_end),
                                                                                           self.time_used)
            softWare = self.comboBox_5.currentText()
            if softWare in ["BEAST1 (NUC)", "BEAST2 (NUC)", "BEAST (AA)"]:
                str1 = self.description + " " + self.parseResults() +\
                    "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n" \
                    "If you use ModelFinder, please cite:\n" + self.reference + \
                    "\n\nhttps://justinbagley.rbind.io/2016/10/11/setting-dna-substitution-models-beast/\nDetails for setting substitution models in %s\n" % softWare
                array = [[i] for i in str1.split(
                    "\n")] + self.model2beast_des() + [[j] for j in ("\n\n" + self.time_used_des).split("\n")]
                self.factory.write_csv_file(
                    self.exportPath + os.sep + "summary.csv", array, self, silence=True)
            else:
                with open(self.exportPath + os.sep + "summary.txt", "w", encoding="utf-8") as f:
                    f.write(self.description + " " + self.parseResults() +
                            "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n"
                            "If you use ModelFinder, please cite:\n" + self.reference + "\n\n" + self.time_used_des)
            if not self.interrupt:
                if self.workflow:
                    # work flow跑的
                    self.startButtonStatusSig.emit(
                        [
                            self.pushButton,
                            self.progressBar,
                            "workflow stop",
                            self.exportPath,
                            self.qss_file,
                            self])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit(
                    [
                        self.pushButton,
                        self.progressBar,
                        "stop",
                        self.exportPath,
                        self.qss_file,
                        self])
            else:
                self.startButtonStatusSig.emit(
                    [
                        self.pushButton,
                        self.progressBar,
                        "except",
                        self.exportPath,
                        self.qss_file,
                        self])
            if not self.workflow:
                self.focusSig.emit(self.exportPath)
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit(
                [
                    self.pushButton,
                    self.progressBar,
                    "except",
                    self.exportPath,
                    self.qss_file,
                    self])

    def guiSave(self):
        # Save geometry
        self.modelfinder_settings.setValue('size', self.size())
        # self.modelfinder_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                index = obj.currentIndex()
                self.modelfinder_settings.setValue(name, index)
            # if isinstance(obj, QLineEdit):
            #     text = obj.text()
            #     tooltip = obj.toolTip()
            #     self.modelfinder_settings.setValue(name, [text, tooltip])
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.modelfinder_settings.setValue(name, state)
            if isinstance(obj, QRadioButton):
                state = obj.isChecked()
                self.modelfinder_settings.setValue(name, state)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.modelfinder_settings.value('size', QSize(500, 500)))
        self.factory.centerWindow(self)
        # self.move(self.modelfinder_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox_6":
                    cpu_num = multiprocessing.cpu_count()
                    list_cpu = ["AUTO"] + [str(i + 1) for i in range(cpu_num)]
                    index = self.modelfinder_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(list_cpu):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
                else:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    index = self.modelfinder_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
            if isinstance(obj, QLineEdit):
                #     text, tooltip = self.modelfinder_settings.value(name, ["", ""])
                #     if os.path.exists(tooltip):
                #         if os.path.exists(tooltip):
                #             obj.setText(text)
                #             obj.setToolTip(tooltip)
                if self.autoMFPath and name == "lineEdit":
                    if os.path.exists(self.autoMFPath):
                        obj.setText(os.path.basename(self.autoMFPath))
                        obj.setToolTip(self.autoMFPath)
                if self.partFile and name == "lineEdit_3":
                    self.inputPartition(self.partFile)
            if isinstance(obj, QCheckBox):
                value = self.modelfinder_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QRadioButton):
                value = self.modelfinder_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        print(exception)
        rgx = re.compile(r'Permission.+?[\'\"](.+\.csv)[\'\"]')
        if rgx.search(exception):
            csvfile = rgx.search(exception).group(1)
            reply = QMessageBox.critical(
                self,
                "Extract sequence",
                "<p style='line-height:25px; height:25px'>Please close '%s' file first!</p>" % os.path.basename(
                    csvfile),
                QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes and platform.system().lower() == "windows":
                os.startfile(csvfile)
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]')
            msg.setWindowTitle("Error")
            msg.setDetailedText(exception)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    def popup_IQTREE_exception(self, text):
        if text.endswith("redo"):
            reply = QMessageBox.question(
                self,
                "ModelFinder",
                "<p style='line-height:25px; height:25px'>%s. Do you want to rerun the analysis and overwrite all output files?</p>" % text.replace(
                    "redo", ""),
                QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.interrupt = False
                self.run_with_CMD(self.command + " -redo")
        else:
            QMessageBox.information(
                self,
                "ModelFinder",
                "<p style='line-height:25px; height:25px'>%s</p>" % text)
            if "Show log" in text:
                self.on_pushButton_9_clicked()

    def closeEvent(self, event):
        self.guiSave()
        self.log_gui.close()  # 关闭子窗口
        self.closeSig.emit("ModelFinder", self.fetchWorkflowSetting())
        # 断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("ModelFinder")
            # 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            # print(self.isRunning())
            reply = QMessageBox.question(
                self,
                "ModelFinder",
                "<p style='line-height:25px; height:25px'>ModelFinder is still running, terminate it?</p>",
                QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    if platform.system().lower() in ["windows", "darwin"]:
                        self.MF_popen.kill()
                    else:
                        os.killpg(os.getpgid(self.MF_popen.pid), signal.SIGTERM)
                    self.MF_popen = None
                    self.interrupt = True
                except:
                    self.MF_popen = None
                    self.interrupt = True
            else:
                event.ignore()

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        name = obj.objectName()
        if isinstance(
                obj,
                QLineEdit):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                if name == "lineEdit":
                    if os.path.splitext(files[0])[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".NXS", ".ALN"]:
                        base = os.path.basename(files[0])
                        self.lineEdit.setText(base)
                        self.lineEdit.setToolTip(files[0])
                    else:
                        QMessageBox.information(
                            self,
                            "ModelFinder",
                            "<p style='line-height:25px; height:25px'>Unsupported file!</p>",
                            QMessageBox.Ok)
                elif name == "lineEdit_2" and os.path.splitext(files[0])[1].upper() in [".NWK", ".NEWICK"]:
                    base = os.path.basename(files[0])
                    self.lineEdit_2.setText(base)
                    self.lineEdit_2.setToolTip(files[0])
                elif name == "lineEdit_3":
                    base = os.path.basename(files[0])
                    self.lineEdit_3.setText(base)
                    self.lineEdit_3.setToolTip(files[0])
        if (event.type() == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # return QMainWindow.eventFilter(self, obj, event) #
        # 其他情况会返回系统默认的事件处理方法。
        return super(ModelFinder, self).eventFilter(obj, event)  # 0

    def gui4Log(self):
        dialog = QDialog(self)
        dialog.resize(800, 500)
        dialog.setWindowTitle("Log")
        gridLayout = QGridLayout(dialog)
        horizontalLayout_2 = QHBoxLayout()
        label = QLabel(dialog)
        label.setText("Log of ModelFinder:")
        horizontalLayout_2.addWidget(label)
        spacerItem = QSpacerItem(
            40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        horizontalLayout_2.addItem(spacerItem)
        toolButton = QToolButton(dialog)
        icon2 = QIcon()
        icon2.addPixmap(
            QPixmap(":/picture/resourses/interface-controls-text-wrap-512.png"))
        toolButton.setIcon(icon2)
        toolButton.setCheckable(True)
        toolButton.setToolTip("Use Wraps")
        toolButton.clicked.connect(self.setWordWrap)
        toolButton.setChecked(True)
        horizontalLayout_2.addWidget(toolButton)
        pushButton = QPushButton("Save to file", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
        pushButton.setIcon(icon)
        pushButton_2 = QPushButton("Close", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png"))
        pushButton_2.setIcon(icon)
        self.textEdit_log = QTextEdit(dialog)
        self.textEdit_log.setReadOnly(True)
        gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2)
        gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2)
        gridLayout.addWidget(pushButton, 2, 0, 1, 1)
        gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
        pushButton.clicked.connect(self.save_log_to_file)
        pushButton_2.clicked.connect(dialog.close)
        dialog.setWindowFlags(
            dialog.windowFlags() | Qt.WindowMinMaxButtonsHint)
        return dialog

    def addText2Log(self, text):
        if re.search(r"\w+", text):
            self.textEdit_log.append(text)
            with open(self.exportPath + os.sep + "PhyloSuite_ModelFinder.log", "a") as f:
                f.write(text + "\n")

    def save_log_to_file(self):
        content = self.textEdit_log.toPlainText()
        fileName = QFileDialog.getSaveFileName(
            self, "ModelFinder", "log", "text Format(*.txt)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(content)

    def setWordWrap(self):
        button = self.sender()
        if button.isChecked():
            button.setChecked(True)
            self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            button.setChecked(False)
            self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap)

    def isRunning(self):
        '''判断程序是否运行,依赖进程是否存在来判断'''
        return hasattr(self, "MF_popen") and self.MF_popen and not self.interrupt

    def controlCodonTable(self, text):
        if text in ["Codon", "DNA-->AA"]:
            self.comboBox_9.setEnabled(True)
        else:
            self.comboBox_9.setEnabled(False)
        if text == "Codon":
            QMessageBox.information(
                self,
                "Information",
                "<p style='line-height:25px; height:25px'>If you choose \"Codon\" sequence type, "
                "only \"IQ-TREE\" is allowed as the \"Model for\" option!</p>")
            self.comboBox_5.setCurrentText("IQ-TREE")

    def run_MF(self):
        rgx_test_model = re.compile(r"^ModelFinder will test (\d+) \w+ models")
        rgx_part_model = re.compile(r"^Loading (\d+) partitions\.\.\.")
        rgx_finished = re.compile(r"^Date and Time:")
        self.totleModels = None
        self.totlePartitions_2 = None
        list_partition_names = []  # 存放partition的名字
        num = 0  # partition出现的次数,当num等于2倍partition的个数的时候,就完成
        is_error = False  ##判断是否出了error
        while True:
            QApplication.processEvents()
            if self.isRunning():
                try:
                    out_line = self.MF_popen.stdout.readline().decode("utf-8", errors="ignore")
                except UnicodeDecodeError:
                    out_line = self.MF_popen.stdout.readline().decode("gbk", errors="ignore")
                if out_line == "" and self.MF_popen.poll() is not None:
                    break
                list_outline = out_line.strip().split()
                if rgx_test_model.search(out_line):
                    self.totleModels = int(
                        rgx_test_model.search(out_line).group(1))
                    self.progressSig.emit(5)
                    self.workflow_progress.emit(5)
                elif rgx_part_model.search(out_line):
                    self.totlePartitions_2 = 2 * \
                        int(rgx_part_model.search(out_line).group(1))
                    self.progressSig.emit(5)
                    self.workflow_progress.emit(5)
                elif self.totleModels and (len(list_outline) == 7) and list_outline[0].isdigit() and list_outline[3].isdigit():
                    # 普通模式
                    model_num = int(list_outline[0])
                    self.progressSig.emit(
                        5 + model_num * 90 / self.totleModels)
                    self.workflow_progress.emit(
                        5 + model_num * 90 / self.totleModels)
                # 下面2个是partition mode
                elif self.totlePartitions_2 and re.search(r"^\d", out_line) and len(out_line.strip().split("\t")) == 8:
                    # partition模式, Subset     Type    Seqs    Sites   Infor
                    # Invar   Model   Name
                    list_partition_names.append(
                        out_line.strip().split("\t")[-1])
                elif list_partition_names and self.totlePartitions_2 and (num <= self.totlePartitions_2) and re.search(r"^ +\d+|^Optimizing", out_line):
                    for i in list_partition_names:
                        if i in out_line:
                            num += 1
                            self.progressSig.emit(
                                5 + num * 90 / self.totlePartitions_2)
                            self.workflow_progress.emit(
                                5 + num * 90 / self.totlePartitions_2)
                    # print(num, self.totlePartitions_2)
                elif rgx_finished.search(out_line):
                    self.progressSig.emit(100)
                    self.workflow_progress.emit(100)
                text = out_line.strip() if re.search(r"\w", out_line) else "\n"
                self.logGuiSig.emit(text)
                if re.search(r"^ERROR", out_line):
                    is_error = True
                    # self.on_pushButton_9_clicked()
                # redo
                if re.search(r"(?m)(^Checkpoint.+?a previous run successfully finished)", out_line):
                    self.interrupt = True
                    text = re.search(
                        r"(?m)(^Checkpoint.+?a previous run successfully finished)", out_line).group(1)
                    self.iq_tree_exception.emit(text + "redo")
            else:
                break
        if is_error:
            self.interrupt = True
            self.iq_tree_exception.emit(
                "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!")
        self.MF_popen = None

    def clear_lineEdit(self):
        sender = self.sender()
        lineEdit = sender.parent()
        lineEdit.setText("")
        lineEdit.setToolTip("")

    def ctrl_ratehet(self, text):
        if text == "IQ-TREE":
            self.checkBox_2.setEnabled(True)
        else:
            self.checkBox_2.setEnabled(False)
        if self.comboBox_3.currentText() == "Codon" and text != "IQ-TREE":
            QMessageBox.information(
                self,
                "Information",
                "<p style='line-height:25px; height:25px'>If you choose \"Codon\" sequence type, "
                "only \"IQ-TREE\" is allowed as the \"Model for\" option!</p>")
            self.comboBox_5.setCurrentText("IQ-TREE")

    def workflow_input(self, MSA=None, partition=None):
        self.lineEdit.setText("")
        self.lineEdit_2.setText("")
        self.lineEdit_3.setText("")
        if MSA:
            MSA = MSA[0] if type(MSA) == list else MSA
            self.lineEdit.setText(os.path.basename(MSA))
            self.lineEdit.setToolTip(MSA)
        if partition:
            self.inputPartition(partition)

    def inputPartition(self, partition):
        if os.path.exists(partition):
            with open(partition, encoding="utf-8", errors='ignore') as f:
                partition_txt = f.read()
            iq_partition = re.search(
                r"(?s)\*\*\*IQ-TREE style\*\*\*(.+?)[\*|$]", partition_txt).group(1).strip()
            path = os.path.dirname(partition) + os.sep + "MF_partition.txt"
            with open(path, "w", encoding="utf-8") as f1:
                f1.write(iq_partition)
            self.lineEdit_3.setText(os.path.basename(path))
            self.lineEdit_3.setToolTip(path)

    def popupAutoDec(self, init=False):
        self.init = init
        self.factory.popUpAutoDetect(
            "ModelFinder", self.workPath, self.auto_popSig, self)

    def popupAutoDecSub(self, popupUI):
        if not popupUI:
            if not self.init:
                QMessageBox.warning(
                    self,
                    "Warning",
                    "<p style='line-height:25px; height:25px'>No available file detected!</p>")
            return
        if not self.init: popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            input_file, partition_file = widget.autoInputs
            self.workflow_input(input_file, partition_file)

    def fetchWorkflowSetting(self):
        '''* if partition mode, partition style:
          * seq type:
          * code table(if codon)
          * criterion
          * model for: '''
        settings = '''<p class="title">***ModelFinder***</p>'''
        ifPartition = "Yes" if self.checkBox.isChecked() else "No"
        settings += '<p>Partition mode: <a href="self.ModelFinder_exe' \
                    ' factory.highlightWidgets(x.checkBox)">%s</a></p>' % ifPartition
        if ifPartition == "Yes":
            partStyle = "Edge-linked" if self.radioButton.isChecked() else "Edge-unlinked"
            settings += '<p>Partition style: <a href="self.ModelFinder_exe' \
                        ' factory.highlightWidgets(x.radioButton,x.radioButton_2)">%s</a></p>' % partStyle
        seq_type = self.comboBox_3.currentText()
        settings += '<p>Sequence type: <a href="self.ModelFinder_exe comboBox_3.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % seq_type
        threads = self.comboBox_6.currentText()
        settings += '<p>Threads: <a href="self.ModelFinder_exe comboBox_6.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % threads
        if seq_type == "Codon":
            code_table = self.comboBox_9.currentText()
            settings += '<p>Code table: <a href="self.ModelFinder_exe comboBox_9.showPopup()' \
                        ' factory.highlightWidgets(x.comboBox_9)">%s</a></p>' % code_table
        criterion = self.comboBox_4.currentText()
        settings += '<p>Criterion: <a href="self.ModelFinder_exe comboBox_4.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_4)">%s</a></p>' % criterion
        modelFor = self.comboBox_5.currentText()
        settings += '<p>Model used for: <a href="self.ModelFinder_exe comboBox_5.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % modelFor
        return settings

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))
        # self.showSig.emit(self)

    def isFileIn(self):
        alignment = self.lineEdit.toolTip()
        if os.path.exists(alignment):
            return alignment
        else:
            return False

    def parseResults(self):
        # 非partition模式
        list_input_model_file = [self.exportPath + os.sep + i for i in os.listdir(self.exportPath) if
                                 os.path.splitext(i)[1].upper() == ".IQTREE"]
        input_model_file = list_input_model_file[
            0] if list_input_model_file else ""
        if input_model_file:
            f = self.factory.read_file(input_model_file)
            # with open(input_model_file, encoding="utf-8", errors='ignore') as f: #"rb",
            model_content = f.read()
            # f.close()
            rgx_model = re.compile(r"(Best-fit model according to.+?\: )(.+)")
            # softWare = self.comboBox_5.currentText()
            # if softWare in ["BEAST1", "BEAST2"]:
            #     prefix = rgx_model.search(model_content).group(1)
            #     model = rgx_model.search(model_content).group(2)
            #     version = softWare[-1]
            #     model_split = model.split("+")[0]
            #     model4beast, addSpecify = self.convertModel2beast(softWare, model_split)
            #     best_model = model.replace(model_split, model4beast)
            #     best_model = prefix + best_model + " [Additional specifications in BEAUti%s: %s]"%(version, addSpecify) \
            #         if addSpecify else prefix + best_model
            # else:
            best_model = rgx_model.search(model_content).group()
        else:
            best_model = ""
        return best_model + "."

    # def convertModel2beast(self, softWare, model):
    #     '''作废'''
    #     if softWare == "BEAST1":
    #         model_replace = {"JC69": ["JC69", ""],
    #                          "TrN": ["TN93", ""],
    #                          "TrNef": ["TN93", 'base Frequencies set to "All Equal"'],
    #                          "K80": ["HKY", 'base Frequencies set to "All Equal"'],
    #                          "K2P": ["HKY", 'base Frequencies set to "All Equal"'],
    #                          "F81": ["GTR", 'turn off operators for all rate parameters'],
    #                          "HKY": ["HKY", ''],
    #                          "SYM": ["GTR", 'base Frequencies set to "All Equal"'],
    #                          "TIM": ["GTR", 'edit XML file by hand so that all other rates are equal to the AG rate'],
    #                          "TVM": ["GTR", 'unchecking AG rate operator'],
    #                          "TVMef": ["GTR", 'unchecking AG rate operator and setting base Frequencies to "All Equal"'],
    #                          "GTR": ["GTR", '']}
    #         if model in model_replace:
    #             return model_replace[model]
    #         else:
    #             return [model, '']
    #     else:
    #         #BEAST2
    #         model_replace = {"JC69": ["JC69", ""],
    #                          "TrN": ["TN93", ""],
    #                          "TrNef": ["TN93", 'base Frequencies set to "All Equal"'],
    #                          "K80": ["HKY", 'base Frequencies set to "All Equal"'],
    #                          "K2P": ["HKY", 'base Frequencies set to "All Equal"'],
    #                          "F81": ["GTR", 'fix all rate parameters to 1.0 (uncheck the "estimate" box)'],
    #                          "HKY": ["HKY", ''],
    #                          "SYM": ["GTR", 'base Frequencies set to "All Equal"'],
    #                          "TIM": ["GTR", 'fix CT and AG rate parameters to 1.0 (uncheck the "estimate" box)'],
    #                          "TVM": ["GTR", 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box)'],
    #                          "TVMef": ["GTR", 'fix the AG rate parameter to 1.0 (uncheck the "estimate" box), '
    #                                         'and also set base Frequencies to "All Equal"'],
    #                          "GTR": ["GTR", '']}
    #         if model in model_replace:
    #             return model_replace[model]
    #         else:
    #             return [model, '']

    def model2beast_des(self):
        softWare = self.comboBox_5.currentText()
        if softWare == "BEAST1 (NUC)":
            return [['Best-fit substitution model', '(Base) Model to select in BEAUti', 'Additional specifications in BEAUti'],
                    ['JC69', 'JC69', 'None '],
                    ['TrN', 'TN93', 'None '],
                    ['TrNef', 'TN93', 'base Frequencies set to "All Equal"'],
                    ['K80 (K2P)', 'HKY',
                     'base Frequencies set to "All Equal"'],
                    ['F81', 'GTR',
                        'turn off operators for all rate parameters'],
                    ['HKY', 'HKY', 'None'],
                    ['SYM', 'GTR', 'base Frequencies set to "All Equal"'],
                    ['TIM', 'GTR',
                     'edit XML file by hand so that all other rates are equal to the AG rate'],
                    ['TVM', 'GTR', 'unchecking AG rate operator'],
                    ['TVMef', 'GTR',
                     'unchecking AG rate operator and setting base Frequencies to "All Equal"'],
                    ['GTR', 'GTR', 'None']]
        elif softWare == "BEAST2 (NUC)":
            return [['Best-fit substitution model', '(Base) Model to select in BEAUti 2', 'Additional specifications in BEAUti 2'],
                    ['JC69', 'JC69', 'None '],
                    ['TrN', 'TN93', 'None'],
                    ['TrNef', 'TN93', 'base Frequencies set to "All Equal"'],
                    ['K80 (K2P)', 'HKY',
                     'base Frequencies set to "All Equal"'],
                    ['F81', 'GTR',
                     'fix all rate parameters to 1.0 (uncheck the "estimate" box)'],
                    ['HKY', 'HKY', ' None'],
                    ['SYM', 'GTR', 'base Frequencies set to "All Equal"'],
                    ['TIM', 'GTR',
                     'fix CT and AG rate parameters to 1.0 (uncheck the "estimate" box)'],
                    ['TVM', 'GTR',
                     'fix the AG rate parameter to 1.0 (uncheck the "estimate" box)'],
                    ['TVMef', 'GTR',
                     'fix the AG rate parameter to 1.0 (uncheck the "estimate" box), and also set base Frequencies to "All Equal"'],
                    ['GTR', 'GTR', ' None']]
        else:
            return []

    def switchPart(self, state):
        if state:
            if not self.workflow:
                self.lineEdit_3.setEnabled(True)
                self.pushButton_22.setEnabled(True)
        else:
            if not self.workflow:
                self.lineEdit_3.setEnabled(False)
                self.pushButton_22.setEnabled(False)

    def getCMD(self):
        alignment = self.isFileIn()
        if alignment:
            # 有数据才执行
            self.output_dir_name = self.factory.fetch_output_dir_name(self.dir_action)
            self.interrupt = False
            self.exportPath = self.factory.creat_dirs(self.workPath +
                                                     os.sep + "ModelFinder_results" + os.sep + self.output_dir_name)
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                # 提醒是否删除旧结果,如果用户取消,就不执行
                return
            os.chdir(self.exportPath)  # 因为用了-pre,所以要先切换目录到该文件夹
            inputFile = os.path.basename(
                shutil.copy(alignment, self.exportPath))
            pre = " -pre \"%s\""%self.factory.refineName(inputFile + "." + self.comboBox_5.currentText().lower().replace("-", "_"))
            # use_model_for = " -m MF" if self.comboBox_5.currentText() == "IQ-TREE" else " -m TESTONLY -mset %s"%self.comboBox_5.currentText().lower()
            if self.comboBox_5.currentText() == "IQ-TREE":
                if self.checkBox_2.isChecked():
                    print(self.lineEdit_3.toolTip())
                    if self.checkBox.isChecked() and self.lineEdit_3.toolTip() and self.lineEdit_3.isEnabled():  # partition
                        #确保勾选了partition以及输入了文件
                        use_model_for = " -m TESTNEWMERGEONLY"
                    else:
                        use_model_for = " -m TESTNEWONLY"
                else:
                    if self.checkBox.isChecked() and self.lineEdit_3.toolTip() and self.lineEdit_3.isEnabled():  # partition
                        use_model_for = " -m TESTMERGEONLY"
                    else:
                        use_model_for = " -m TESTONLY"
            elif self.comboBox_5.currentText() in ["BEAST1 (NUC)", "BEAST2 (NUC)"]:
                use_model_for = " -mset JC69,TrN,TrNef,K80,K2P,F81,HKY,SYM,TIM,TVM,TVMef,GTR -mrate E,G"
            elif self.comboBox_5.currentText() in ["BEAST (AA)"]:
                use_model_for = " -mset Blosum62,cpREV,JTT,mtREV,WAG,LG,Dayhoff -mrate E,G"
            else:
                use_model_for = " -m TESTONLY -mset %s" % self.comboBox_5.currentText().lower() \
                    if ((not self.checkBox.isChecked()) or (not self.lineEdit_3.toolTip())) else " -m TESTMERGEONLY -mset %s" % self.comboBox_5.currentText().lower()
            criterion_org = re.search(
                r"\((\w+)\)", self.comboBox_4.currentText()).group(1)
            criterion = " -%s" % criterion_org if criterion_org != "BIC" else ""
            dict_seq = {"DNA": "DNA", "Protein": "AA", "Codon": "CODON", "Binary": "BIN", "Morphology": "MORPH",
                        "DNA-->AA": "NT2AA"}
            seqType = " -st %s" % dict_seq[
                self.comboBox_3.currentText()] if self.comboBox_3.currentText() != "Auto detect" else ""
            codon_table_index = self.comboBox_9.currentText().split(" ")[0]
            seqType = seqType + \
                codon_table_index if seqType in [
                    " -st CODON", " -st NT2AA"] else seqType
            treeFile = " -te %s" % shutil.copy(self.lineEdit_2.toolTip(),
                                               self.exportPath) if self.lineEdit_2.toolTip() else ""
            partitionCMD = "-spp" if self.radioButton.isChecked() else "-sp"
            partFile = " %s \"%s\"" % (
                partitionCMD, os.path.basename(shutil.copy(self.lineEdit_3.toolTip(), self.exportPath))) if (
                self.checkBox.isChecked() and self.lineEdit_3.toolTip()) else ""
            threads = " -nt %s" % self.comboBox_6.currentText()
            command = self.modelfinder_exe + " -s \"%s\"" % inputFile + \
                use_model_for + criterion + seqType + \
                treeFile + partFile + threads + pre
            # print(self.command)
            self.textEdit_log.clear()  # 清空
            # 描述
            type = "Edge-linked" if self.radioButton.isChecked() else "Edge-unlinked"
            model = "best-fit model" if not partFile else "best-fit partition model (%s)" % type
            self.description = '''ModelFinder (Kalyaanamoorthy et al., 2017) was used to select the %s using %s criterion.''' % (
                model, criterion_org)
            self.reference = "Kalyaanamoorthy, S., Minh, B.Q., Wong, T.K.F., von Haeseler, A., Jermiin, L.S., 2017. ModelFinder: fast model selection for accurate phylogenetic estimates. Nat. Methods 14, 587-589."
            return command
        else:
            QMessageBox.critical(
                self,
                "ModelFinder",
                "<p style='line-height:25px; height:25px'>Please input alignment file first!</p>")
            return None

    def showCMD(self):
        """
        show command
        """
        self.command = self.getCMD()
        if self.command:
            dialog = QDialog(self)
            dialog.resize(600, 200)
            dialog.setWindowTitle("Command")
            gridLayout = QGridLayout(dialog)
            label = QLabel(dialog)
            label.setText("Current Command:")
            pushButton = QPushButton("Save and run", dialog)
            icon = QIcon()
            icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
            pushButton.setIcon(icon)
            pushButton_2 = QPushButton("Close", dialog)
            icon = QIcon()
            icon.addPixmap(
                QPixmap(":/picture/resourses/if_Delete_1493279.png"))
            pushButton_2.setIcon(icon)
            self.textEdit_cmd = QTextEdit(dialog)
            self.textEdit_cmd.setText(self.command)
            gridLayout.addWidget(label, 0, 0, 1, 2)
            gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2)
            gridLayout.addWidget(pushButton, 2, 0, 1, 1)
            gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
            pushButton.clicked.connect(
                lambda: [self.run_with_CMD(self.textEdit_cmd.toPlainText()), dialog.close()])
            pushButton_2.clicked.connect(dialog.close)
            dialog.setWindowFlags(
                dialog.windowFlags() | Qt.WindowMinMaxButtonsHint)
            dialog.exec_()

    def run_with_CMD(self, cmd):
        self.command = cmd
        if self.command:
            self.MF_popen = self.factory.init_popen(self.command)
            self.factory.emitCommands(self.logGuiSig, self.command)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
Пример #4
0
class ExtractGB(QDialog, Ui_Extractor, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    threadFinished = pyqtSignal()
    startButtonStatusSig = pyqtSignal(list)

    def __init__(
            self,
            gb_files=None,
            list_names=None,
            workPath=None,
            totleID=None,
            clearFolderSig=None,
            focusSig=None,
            parent=None):
        super(ExtractGB, self).__init__(parent)
        self.parent = parent
        self.setupUi(self)
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.gb_files = gb_files
        self.workPath = workPath
        self.totleID = totleID
        self.clearFolderSig = clearFolderSig
        self.list_names = list_names
        self.focusSig = focusSig
        self.installEventFilter(self)
        self.dict_icon = {
            "rectangle": ":/itol_domain/resourses/itol/re.png",
            "horizontal hexagon": ":/itol_domain/resourses/itol/hh.png",
            "vertical hexagon": ":/itol_domain/resourses/itol/hv.png",
            "ellipse": ":/itol_domain/resourses/itol/el.png",
            "rhombus (diamond)": ":/itol_domain/resourses/itol/di.png",
            "right pointing triangle": ":/itol_domain/resourses/itol/tr.png",
            "left pointing triangle": ":/itol_domain/resourses/itol/tl.png",
            "left pointing pentagram": ":/itol_domain/resourses/itol/pl.png",
            "right pointing pentagram": ":/itol_domain/resourses/itol/pr.png",
            "up pointing pentagram": ":/itol_domain/resourses/itol/pu.png",
            "down pointing pentagram": ":/itol_domain/resourses/itol/pd.png",
            "octagon": ":/itol_domain/resourses/itol/oc.png",
            "rectangle (gap)": ":/itol_domain/resourses/itol/gp.png"}

        self.dict_shape = {
            "rectangle": "RE",
            "horizontal hexagon": "HH",
            "vertical hexagon": "HV",
            "ellipse": "EL",
            "rhombus (diamond)": "DI",
            "right pointing triangle": "TR",
            "left pointing triangle": "TL",
            "left pointing pentagram": "PL",
            "right pointing pentagram": "PR",
            "up pointing pentagram": "PU",
            "down pointing pentagram": "PD",
            "octagon": "OC",
            "rectangle (gap)": "GP"}

        self.showOn()
        self.extractGB_settings = QSettings(
            self.thisPath + '/settings/extractGB_settings.ini', QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.extractGB_settings.setFallbacksEnabled(False)
        self.settings_ini = QSettings(self.thisPath + '/settings/setting_settings.ini', QSettings.IniFormat)
        self.settings_ini.setFallbacksEnabled(False)
        # 保存主界面设置
        self.mainwindow_settings = QSettings(
            self.thisPath + '/settings/mainwindow_settings.ini', QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.mainwindow_settings.setFallbacksEnabled(False)
        # 恢复用户的设置
        self.guiRestore()
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)

        self.progressSig.connect(self.runProgress)
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.tableWidget_2.installEventFilter(self)
        self.table_popMenu = QMenu(self)
        self.Copy = QAction("Copy", self,
                              statusTip="Copy color(s)",
                              shortcut="Ctrl+C",
                              triggered=self.copyColor)
        self.Cut = QAction("Cut", self,
                              statusTip="Cut color(s)",
                              shortcut="Ctrl+X",
                              triggered=self.cutColor)
        self.Paste = QAction("Paste", self,
                              statusTip="Paste color(s)",
                              shortcut="Ctrl+V",
                              triggered=self.pasteColor)
        self.remove = QAction("Delete", self,
                                  shortcut=QKeySequence.Delete,
                                  statusTip="Remove color(s)",
                                  triggered=self.on_pushButton_9_clicked)
        self.table_popMenu.addAction(self.Copy)
        self.table_popMenu.addAction(self.Cut)
        self.table_popMenu.addAction(self.Paste)
        self.table_popMenu.addAction(self.remove)
        self.tableWidget_2.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableWidget_2.customContextMenuRequested.connect(lambda x: self.table_popMenu.exec_(QCursor.pos()))
        self.comboBox_6.activated[str].connect(self.switchSeqType)
        # self.checkBox.toggled.connect(self.judgeCodonW)
        self.switchSeqType(self.comboBox_6.currentText())
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "", menu)
        self.work_action.triggered.connect(lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"), "Output Dir: ", menu)
        self.dir_action.triggered.connect(lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton_2.toolButton.setMenu(menu)
        self.pushButton_2.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True, parent=self)  # 初始化一下
        ## brief demo
        self.label_3.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(
            "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-1-1-Brief-example")))

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        """
        execute program
        """
        self.dict_args = {}
        self.dict_args["progressSig"] = self.progressSig
        self.dict_args["gb_files"] = self.gb_files
        self.dict_args["workPath"] = self.workPath
        # 创建输出文件夹
        self.output_dir_name = self.factory.fetch_output_dir_name(self.dir_action)
        self.exportPath = self.factory.creat_dirs(self.workPath +
                                                        os.sep + "extract_results" + os.sep + self.output_dir_name)
        self.dict_args["exportPath"] = self.exportPath
        self.dict_args["codon"] = str(
            self.comboBox_9.currentText()).split(" ")[0]
        self.dict_args["Name Type"] = [self.comboBox_4.itemText(i) for i in range(self.comboBox_4.count())
                                               if self.comboBox_4.model().item(i).checkState() == Qt.Checked]
        # self.dict_args["Gene Number"] = str(
        #     self.comboBox_8.currentText())
        # bool
        self.dict_args["if ignore duplicated"] = True #self.checkBox_2.isChecked()
        self.dict_args["if statistics"] = True #self.checkBox_3.isChecked()
        self.dict_args["if itol"] = self.groupBox_4.isChecked()
        self.dict_args["totleID"] = self.totleID
        self.dict_args["seq type"] = str(self.comboBox_6.currentText())
        for row, row_text in enumerate(
                ["atp", "nad", "cytb", "cox", "rRNA", "tRNA", "NCR"]):
            for column, column_text in enumerate(
                    ["checked", "colour", "length", "shape"]):
                if column == 0:
                    self.dict_args[row_text + column_text] = True if self.tableWidget.item(
                        row, column).checkState() == Qt.Checked else False  # bool
                elif column == 1:
                    self.dict_args[row_text +
                                   column_text] = self.tableWidget.item(row, column).text()
                elif column == 2:
                    self.dict_args[row_text +
                                   column_text] = self.tableWidget.item(row, column).text()
                elif column == 3:
                    shape_combo = self.tableWidget.cellWidget(row, column)
                    shape = shape_combo.currentText()
                    self.dict_args[row_text +
                                   column_text] = self.dict_shape[shape]  # 转为缩写形式
        self.dict_args["gene interval"] = self.doubleSpinBox.value()
        self.dict_args["included_lineages"] = [self.comboBox_7.itemText(i) for i in range(self.comboBox_7.count())
                                               if self.comboBox_7.model().item(i).checkState() == Qt.Checked]
        # {"Family":["red", "green", ""]}
        dict_color_array = self.getLineageColor()
        for i in dict_color_array:
            while "" in dict_color_array[i]:
                # 删除空行
                dict_color_array[i].remove("")
        self.dict_args["lineage color"] = dict_color_array
        ##提取的设置
        dict_extract_settings = copy.deepcopy(self.dict_gbExtract_set[self.comboBox_6.currentText()])
        #提取所有的话,记得先判断有没有那个键
        extract_all_features = dict_extract_settings.pop("extract all features") if "extract all features" \
                                                                                    in dict_extract_settings else False
        self.dict_args["extract_intergenic_regions"] = dict_extract_settings.pop("extract intergenic regions") if "extract intergenic regions" \
                                                                                    in dict_extract_settings else True
        self.dict_args["extract_overlapping_regions"] = dict_extract_settings.pop("extract overlapping regions") if "extract overlapping regions" \
                                                                             in dict_extract_settings else True
        self.dict_args["intergenic_regions_threshold"] = dict_extract_settings.pop("intergenic regions threshold") if "intergenic regions threshold" \
                                                                             in dict_extract_settings else 200
        self.dict_args["overlapping_regions_threshold"] = dict_extract_settings.pop(
            "overlapping regions threshold") if "overlapping regions threshold" \
                                               in dict_extract_settings else 1
        self.dict_args["features"] = dict_extract_settings.pop("Features to be extracted") if not extract_all_features else "All"
        # self.dict_args["features"] = "All" if self.extract_all_features else self.dict_args["features"]
        name_unify = dict_extract_settings.pop("Names unification")
        self.dict_args["replace"] = {i[0]: i[1] for i in name_unify}
        self.dict_args["extract_list_gene"] = dict_extract_settings.pop("extract listed gene") if "extract listed gene" \
                                                                                    in dict_extract_settings else False
        self.dict_args["qualifiers"] = dict_extract_settings  ###只剩下qualifier的设置
        self.dict_args["extract_entire_seq"] = self.radioButton.isChecked()
        self.dict_args["entire_seq_name"] = self.lineEdit.text() if self.lineEdit.text() else "sequence"
        ok = self.factory.remove_dir(self.dict_args["exportPath"], parent=self)
        if not ok:
            # 提醒是否删除旧结果,如果用户取消,就不执行
            return
        self.worker = WorkThread(self.run_command, parent=self)
        self.worker.start()

    def run_command(self):
        try:
            # 清空文件夹再生成结果,放在这里执行好统一管理报错
            # self.clearFolderSig.emit(self.dict_args["exportPath"])
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit(
                [self.pushButton_2, self.progressBar, "start", self.dict_args["exportPath"], self.qss_file, self])
            if (self.dict_args["seq type"] == "Mitogenome") and (not self.dict_args["extract_entire_seq"]):
                # 如果是整个序列提取,也不能用这个
                extract = GBextract_MT(**self.dict_args)
                extract._exec()
            else:
                extract = GBextract(**self.dict_args)
                extract._exec()
            # extract = GBextract(**self.dict_args)
            if extract.Error_ID:
                stopStatus = extract.Error_ID
            elif extract.source_feature_IDs:
                stopStatus = "extract_no_feature%s"%", ".join(extract.source_feature_IDs)
            else:
                stopStatus = "stop"
            self.startButtonStatusSig.emit(
                [self.pushButton_2, self.progressBar, stopStatus, self.dict_args["exportPath"], self.qss_file, self])
            self.focusSig.emit(self.dict_args["exportPath"])
            time_end = datetime.datetime.now()
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (str(time_start), str(time_end),
                                                                                  str(time_end - time_start))
            with open(self.dict_args["exportPath"] + os.sep + "summary.txt", "w", encoding="utf-8") as f:
                f.write("If you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., "
                        "Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop "
                        "platform for streamlined molecular sequence data management and evolutionary phylogenetics "
                        "studies. bioRxiv, doi: 10.1101/489088.\n\n" + self.time_used_des
                        + "For the summary of this extraction, please see \"overview.csv\"")
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit(
                [self.pushButton_2, self.progressBar, "except", self.dict_args["exportPath"], self.qss_file, self])

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        self.close()

    @pyqtSlot()
    def on_pushButton_6_clicked(self):
        """
        Add row
        """
        currentRows = self.tableWidget_2.rowCount()
        self.tableWidget_2.setRowCount(currentRows + 1)
        for column in range(self.tableWidget_2.columnCount()):
            item = QTableWidgetItem("")
            item.setToolTip("Double click to set colors")
            self.tableWidget_2.setItem(currentRows, column, item)

    @pyqtSlot()
    def on_pushButton_8_clicked(self):
        """
        delete row
        """
        selecteditems = self.tableWidget_2.selectedItems()
        rows = sorted(set([i.row() for i in selecteditems]), reverse=True)
        for row in rows:
            self.tableWidget_2.removeRow(row)

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        delete cell
        """
        selecteditems = self.tableWidget_2.selectedItems()
        if selecteditems:
            for i in selecteditems:
                i.setText("")
                i.setBackground(QColor('transparent'))

    @pyqtSlot()
    def on_toolButton_3_clicked(self):
        """
        GenBank file extract settings
        """
        self.extract_setting = ExtractSettings(self)
        self.extract_setting.closeSig.connect(self.displaySettings)
        # 添加最大化按钮
        self.extract_setting.setWindowFlags(self.extract_setting.windowFlags() | Qt.WindowMinMaxButtonsHint)
        self.extract_setting.exec_()

    @pyqtSlot()
    def on_pushButton_10_clicked(self):
        """
        GenBank file lineage recognization settings
        """
        self.setting = Setting(self)
        self.setting.display_table(self.setting.listWidget.item(0))
        self.setting.closeSig.connect(self.updateLineageCombo)
        self.setting.closeSig.connect(self.updateLineageTable)
        self.setting.setWindowFlags(self.setting.windowFlags() | Qt.WindowMinMaxButtonsHint)
        self.setting.exec_()

    def showOn(self):
        list_color = [
            "#ffff33",
            "#99ffff",
            "#ff9999",
            "#6699ff",
            "#DAA520",
            "#ccff00",
            "#bfbfbf"]
        self.tableWidget.itemClicked.connect(self.handleItemClicked)
        for row in range(self.tableWidget.rowCount()):
            # 2列颜色 (必须初始化一个item,不然恢复界面设置会出错)
            item2 = QTableWidgetItem(list_color[row])
            item2.setBackground(QColor(list_color[row]))
            self.tableWidget.setItem(row, 1, item2)
            model = QStandardItemModel()
            for i in self.dict_icon:
                item = QStandardItem(i)
                item.setIcon(QIcon(self.dict_icon[i]))
                font = item.font()
                font.setPointSize(13)
                item.setFont(font)
                model.appendRow(item)
            comb_box = MyComboBox(self)
            comb_box.setModel(model)
            # 改变icon大小
            view = comb_box.view()
            view.setIconSize(QSize(38, 38))
            self.tableWidget.setCellWidget(row, 3, comb_box)
        self.tableWidget.resizeColumnsToContents()
        self.tableWidget.verticalHeader().setVisible(False)

    def handleItemClicked(self, item):
        if item.column() == 1:
            color = QColorDialog.getColor(QColor(item.text()), self)
            if color.isValid():
                item.setText(color.name())
                item.setBackground(color)
            self.tableWidget.clearSelection()

    def handleItemClicked_2(self, item):
        color = QColorDialog.getColor(QColor(item.text()), self)
        if color.isValid():
            item.setText(color.name())
            item.setBackground(color)
        self.tableWidget_2.clearSelection()

    def guiSave(self):
        # Save geometry
        self.extractGB_settings.setValue('size', self.size())
        # self.extractGB_settings.setValue('pos', self.pos())
        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                if name in ["comboBox_7", "comboBox_4"]:
                    dict_state = {}
                    for i in range(obj.count()):
                        text = obj.itemText(i)
                        state = 2 if obj.model().item(i).checkState() == Qt.Checked else 0
                        dict_state[text] = state
                    self.extractGB_settings.setValue(name, dict_state)
                elif name != "comboBox_5":
                    text = obj.currentText()
                    if text:
                        allItems = [
                            obj.itemText(i) for i in range(obj.count())]
                        allItems.remove(text)
                        sortItems = [text] + allItems
                        self.extractGB_settings.setValue(name, sortItems)
            if isinstance(obj, QGroupBox):
                state = obj.isChecked()
                self.extractGB_settings.setValue(name, state)
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.extractGB_settings.setValue(name, state)
            if isinstance(obj, QRadioButton):
                state = obj.isChecked()
                self.extractGB_settings.setValue(name, state)
            if isinstance(obj, QTableWidget):
                if name == "tableWidget":
                    array = []  # 每一行存:checked, color, length, index_text
                    for row in range(obj.rowCount()):
                        checked = "true" if obj.item(
                            row, 0).checkState() == Qt.Checked else "false"
                        colour = obj.item(row, 1).text()
                        length = obj.item(row, 2).text()
                        shape_combo = obj.cellWidget(row, 3)
                        shape = shape_combo.currentText()
                        array.append([checked, colour, length, shape])
                    self.extractGB_settings.setValue(name, array)
                elif name == "tableWidget_2":
                    dict_color_array = self.getLineageColor()
                    self.extractGB_settings.setValue(name, dict_color_array)
            if isinstance(obj, QTabWidget):
                index = obj.currentIndex()
                self.extractGB_settings.setValue(name, index)
            if isinstance(obj, QDoubleSpinBox):
                value = obj.value()
                self.extractGB_settings.setValue(name, value)
            if isinstance(obj, QLineEdit):
                text = obj.text()
                self.extractGB_settings.setValue(name, text)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.extractGB_settings.value('size', QSize(571, 680)))
        if self.height() < 640:
            self.resize(QSize(571, 650))
        # self.move(self.extractGB_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox_5":
                    # 展示输入
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(self.list_names):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    self.changeLable()
                elif name == "comboBox_6":
                    self.displaySettings()
                elif name == "comboBox_7":
                    self.updateLineageCombo()
                elif name == "comboBox_4":
                    key = re.sub(r"/|\\", "_", self.workPath) + "_availableInfo"
                    value = self.mainwindow_settings.value(
                        key, None)
                    if value:
                        source_keys = value[3][1]
                    else:
                        source_keys = None
                    items = ["Organism", "ID", "Name", "Length", "Description", "Date"] + source_keys \
                        if source_keys else ["Organism", "ID", "Name", "Length"]
                    ini_state = {}.fromkeys(items, 0)  # 2代表选中
                    ini_state["Organism"] = 2
                    ini_state["Name"] = 2
                    dict_name_state = self.extractGB_settings.value("comboBox_4", ini_state)
                    if type(dict_name_state) != dict:
                        dict_name_state = ini_state
                    self.comboBox_4.sep = "_"
                    model = self.comboBox_4.model()
                    self.comboBox_4.clear()
                    for num, text in enumerate(items):
                        item = QStandardItem(str(text))
                        state = dict_name_state[text] if text in dict_name_state else ini_state[text]  # 测试一下
                        item.setCheckState(int(state))
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    self.comboBox_4.setTopText()
                    self.comboBox_4.view().pressed.connect(self.judgeName)
                else:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    values = self.extractGB_settings.value(name, allItems)
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(values):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
            if isinstance(obj, QGroupBox):
                # 如果没有name,就设置为True
                state = self.extractGB_settings.value(name, "true")
                obj.setChecked(self.factory.str2bool(state))
            if isinstance(obj, QCheckBox):
                value = self.extractGB_settings.value(
                    name, "no setting")  # get stored value from registry
                if value != "no setting":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QRadioButton):
                value = self.extractGB_settings.value(
                    name, "first")  # get stored value from registry
                if value != "first":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QTableWidget):
                if name == "tableWidget":
                    # 每一行存:checked, color, length, index_text
                    ini_array = [
                        [
                            'true', '#ffff33', '25', 'rectangle'], [
                            'true', '#99ffff', '25', 'rectangle'], [
                            'true', '#ff9999', '30', 'rectangle'], [
                            'true', '#6699ff', '25', 'rectangle'], [
                            'true', '#DAA520', '18', 'rectangle'], [
                                'true', '#ccff00', '15', 'rectangle'], [
                                    'false', '#bfbfbf', '15', 'ellipse']]
                    array = self.extractGB_settings.value(name, ini_array)
                    for row in range(obj.rowCount()):
                        ifChecked = Qt.Checked if array[row][
                            0] == "true" else Qt.Unchecked
                        obj.item(row, 0).setCheckState(ifChecked)
                        obj.item(row, 1).setText(array[row][1])
                        obj.item(row, 1).setBackground(QColor(array[row][1]))
                        obj.item(row, 2).setText(array[row][2])
                        shape = array[row][3]
                        shape_combo = obj.cellWidget(row, 3)
                        shape_combo_index = shape_combo.findText(shape)
                        if shape_combo_index == -1:  # add to list if not found
                            shape_combo.insertItems(0, [value])
                            index = shape_combo.findText(value)
                            shape_combo.setCurrentIndex(index)
                        else:
                            # preselect a combobox value by index
                            shape_combo.setCurrentIndex(shape_combo_index)
                elif name == "tableWidget_2":
                    self.updateLineageTable()
            if isinstance(obj, QTabWidget):
                index = self.extractGB_settings.value(name, 0)
                obj.setCurrentIndex(int(index))
            if isinstance(obj, QDoubleSpinBox):
                value = self.extractGB_settings.value(name, 1.0)
                obj.setValue(float(value))
            if isinstance(obj, QLineEdit):
                text = self.extractGB_settings.value(name, "first")
                if text != "first":
                    obj.setText(text)

    def popupException(self, exception):
        rgx = re.compile(r'Permission.+?[\'\"](.+\.csv)[\'\"]')
        if rgx.search(exception):
            csvfile = rgx.search(exception).group(1)
            reply = QMessageBox.critical(
                self,
                "Extract sequence",
                "<p style='line-height:25px; height:25px'>Please close '%s' file first!</p>"%os.path.basename(csvfile),
                QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes and platform.system().lower() == "windows":
                os.startfile(csvfile)
        elif "Permission" in exception:
            reply = QMessageBox.critical(
                self,
                "Extract sequence",
                "<p style='line-height:25px; height:25px'>Error happened, please close the window and try again!</p>",
                QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                self.close()
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]')
            msg.setWindowTitle("Error")
            msg.setDetailedText(exception)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    def closeEvent(self, event):
        self.guiSave()

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def eventFilter(self, obj, event):
        name = obj.objectName()
        if isinstance(obj, QTableWidget):
            if event.type() == QEvent.KeyPress:  # 首先得判断type
                modifiers = QApplication.keyboardModifiers()
                if name == "tableWidget_2":
                    if event.key() == Qt.Key_Delete:
                        self.on_pushButton_9_clicked()
                        return True
                    if (modifiers == Qt.ControlModifier) and (
                                event.key() == Qt.Key_C):
                        self.copyColor()
                        return True
                    if (modifiers == Qt.ControlModifier) and (
                                event.key() == Qt.Key_X):
                        self.cutColor()
                        return True
                    if (modifiers == Qt.ControlModifier) and (
                                event.key() == Qt.Key_V):
                        self.pasteColor()
                        return True
        if (event.type() == QEvent.Show) and (obj == self.pushButton_2.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+",
                         self.dir_action.text()) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton_2.toolButton.menu().pos().x()
            menu_width = self.pushButton_2.toolButton.menu().size().width()
            button_width = self.pushButton_2.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton_2.toolButton.menu().pos().y())
            self.pushButton_2.toolButton.menu().move(pos)
            return True
        return super(ExtractGB, self).eventFilter(obj, event)

    def changeLable(self):
        count = str(self.comboBox_5.count())
        self.label_4.setText("Inputs (" + count + "):")

    def getLineageColor(self):
        dict_color_array = OrderedDict()
        columnNum = self.tableWidget_2.columnCount()
        rowNum = self.tableWidget_2.rowCount()
        for column in range(columnNum):
            headerText = self.tableWidget_2.horizontalHeaderItem(column).text()
            list_columnText = []
            for row in range(rowNum):
                if self.tableWidget_2.item(row, column):
                    list_columnText.append(
                        self.tableWidget_2.item(row, column).text())
            dict_color_array[headerText] = list_columnText
        return dict_color_array

    def copyColor(self):
        selecteditems = self.tableWidget_2.selectedItems()
        if selecteditems:
            list_selItem_text = [i.text() for i in selecteditems]
            QApplication.clipboard().setText("\t".join(list_selItem_text))

    def cutColor(self):
        selecteditems = self.tableWidget_2.selectedItems()
        list_selItem_text = []
        if selecteditems:
            for i in selecteditems:
                list_selItem_text.append(i.text())
                i.setText("")
                i.setBackground(QColor('transparent'))
            QApplication.clipboard().setText("\t".join(list_selItem_text))

    def pasteColor(self):
        colors = QApplication.clipboard().text()
        list_colors = re.split(r"\s+|,", colors)
        while "" in list_colors:
            list_colors.remove("")
        selecteditems = self.tableWidget_2.selectedItems()
        for num, i in enumerate(selecteditems):
            if (num+1) <= len(list_colors):
                color = list_colors[num]
                qcolor = QColor(color)
                if not qcolor.isValid():
                    continue
                i.setText(color)
                i.setBackground(qcolor)

    def displaySettings(self):
        ###提取的设置
        self.GenBankExtract_settings = QSettings(
            self.thisPath + '/settings/GenBankExtract_settings.ini', QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.GenBankExtract_settings.setFallbacksEnabled(False)
        self.dict_gbExtract_set = self.GenBankExtract_settings.value("set_version")
        # self.extract_list_gene = self.factory.str2bool(self.GenBankExtract_settings.value("extract listed gene",
        #                                                                                   "false"))
        # self.extract_all_features = self.factory.str2bool(self.GenBankExtract_settings.value("extract all features",
        #
                                                                                     # "false"))
        if self.dict_gbExtract_set:
            self.allVersions = list(self.dict_gbExtract_set.keys())
            model = self.comboBox_6.model()
            self.comboBox_6.clear()
            for num, i in enumerate(self.allVersions + [">>>More<<<"]):
                item = QStandardItem(i)
                # 背景颜色
                if num % 2 == 0:
                    item.setBackground(QColor(255, 255, 255))
                else:
                    item.setBackground(QColor(237, 243, 254))
                model.appendRow(item)
        self.switchSeqType(self.comboBox_6.currentText())

    def switchSeqType(self, text):
        ###保存version顺序
        if text == ">>>More<<<":
            QDesktopServices.openUrl(QUrl("https://dongzhang0725.github.io/dongzhang0725.github.io/PhyloSuite-demo/customize_extraction/"))
            self.comboBox_6.setCurrentIndex(0)
            return
        if text != self.allVersions[0]:
            if text in self.allVersions:
                list_now_versions = copy.deepcopy(self.allVersions)
                list_now_versions.remove(text)
                reorder_list = [text] + list_now_versions
                self.dict_gbExtract_set = OrderedDict((i, self.dict_gbExtract_set[i]) for i in reorder_list)
                self.GenBankExtract_settings.setValue('set_version', self.dict_gbExtract_set)
        ###控制itol
        if text != "Mitogenome":
            targetIndex = "null"
            for index in range(self.tabWidget.count()):
                if self.tabWidget.tabText(index) == "Gene order display":
                    targetIndex = index
            if targetIndex != "null":
                self.hiddenIndex = targetIndex
                self.hiddenWidget = self.tabWidget.widget(self.hiddenIndex)
                self.hiddenTabText = self.tabWidget.tabText(self.hiddenIndex)
                self.tabWidget.removeTab(self.hiddenIndex)
                self.hiddenFlag = True
        else:
            if hasattr(self, "hiddenFlag") and self.hiddenFlag:
                self.tabWidget.insertTab(self.hiddenIndex, self.hiddenWidget, self.hiddenTabText)
                self.hiddenFlag = False
        self.tabWidget.setCurrentIndex(0)

    def updateLineageTable(self):
        # tableWidget_2
        self.tableWidget_2.itemDoubleClicked.connect(self.handleItemClicked_2)
        ini_data = [['Class', 'Order', 'Superfamily', 'Family', 'Subfamily', 'Genus'],
                    [['', '*tera', '*dea', '*dae', '*nae', ''],
                     ['', '', '*ida', '', '', ''],
                     ['', '', '', '', '', ''],
                     ['', '', '', '', '', ''],
                     ['', '', '', '', '', '']]]
        header, array = self.settings_ini.value("Taxonomy Recognition", ini_data)
        countColumn = len(header)
        self.tableWidget_2.setColumnCount(countColumn)
        ini_dict_array = OrderedDict()
        for i in header:
            ini_dict_array[i] = [""] * 6
        familyColor = [
            "#81C7D6", "#FF0033", "#6A00D1", "#49BF4E", "#AA538B", "#FF99CC"]
        ini_dict_array[
            "Family"] = familyColor if "Family" in ini_dict_array else ini_dict_array["Family"]
        self.tableWidget_2.setHorizontalHeaderLabels(
            header)
        dict_array = self.extractGB_settings.value(
            "tableWidget_2", ini_dict_array)
        maxRow = len(max(list(dict_array.values()), key=len))
        self.tableWidget_2.setRowCount(maxRow)
        if len(ini_dict_array) > len(dict_array):
            # 如果用户添加了lineage
            list_differ = list(
                set(ini_dict_array.keys()).difference(set(dict_array.keys())))
            for i in list_differ:
                # 加上这个lineage
                dict_array[i] = [""] * maxRow
        for column, columnText in enumerate(header):
            for row, rowText in enumerate(dict_array[columnText]):
                item = QTableWidgetItem(rowText)
                item.setToolTip("Double click to set colors")
                color = QColor(rowText)
                if color.isValid():
                    item.setBackground(color)
                self.tableWidget_2.setItem(row, column, item)

    def updateLineageCombo(self):
        ini_data = [['Class', 'Order', 'Superfamily', 'Family', 'Subfamily', 'Genus'],
                    [['', '*tera', '*dea', '*dae', '*nae', ''],
                     ['', '', '*ida', '', '', ''],
                     ['', '', '', '', '', ''],
                     ['', '', '', '', '', ''],
                     ['', '', '', '', '', '']]]
        header, array = self.settings_ini.value("Taxonomy Recognition", ini_data)
        ini_state = {}.fromkeys(header, 2)  # 2代表选中
        dict_lng_state = self.extractGB_settings.value("comboBox_7", ini_state)
        model = self.comboBox_7.model()
        self.comboBox_7.clear()
        for num, text in enumerate(header):
            item = QStandardItem(text)
            state = dict_lng_state[text] if text in dict_lng_state else ini_state[text]  # 测试一下
            item.setCheckState(int(state))
            # 背景颜色
            if num % 2 == 0:
                item.setBackground(QColor(255, 255, 255))
            else:
                item.setBackground(QColor(237, 243, 254))
            model.appendRow(item)
        self.comboBox_7.setTopText()

    def judgeName(self):
        if not set(["Organism", "ID", "Name"]).intersection(set(self.comboBox_4.topText.split("_"))):
            QMessageBox.information(
                self,
                "Information",
                "<p style='line-height:25px; height:25px'>At least one of the following should be retained: \"Organism\", \"ID\", \"Name\"</p>")
            self.comboBox_4.item.setCheckState(Qt.Checked)
            self.comboBox_4.setTopText()

    def judgeCodonW(self, bool_):
        if bool_:
            pass
Пример #5
0
class SerhNCBI(QMainWindow, Ui_SerhNCBI, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    updateSig = pyqtSignal(list)
    searchSig = pyqtSignal(str)
    progressBarSig = pyqtSignal(int)  # 控制进度条
    progressDiologSig = pyqtSignal(int)
    ctrlItemsSig = pyqtSignal(int)
    inputContentSig = pyqtSignal(str, str)
    downloadFinished = pyqtSignal()
    fastaDownloadFinishedSig = pyqtSignal(str)

    def __init__(self, workPath=None, parent=None):
        super(SerhNCBI, self).__init__(parent)
        # self.thisPath = os.path.dirname(os.path.realpath(__file__))
        self.parent = parent
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.setupUi(self)
        # 保存设置
        self.serhNCBI_settings = QSettings(
            self.thisPath + '/settings/serhNCBI_settings.ini',
            QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.serhNCBI_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        # self.progressSig.connect(self.runProgress)
        # self.logGuiSig.connect(self.addText2Log)
        self.lineEdit.buttonSearch.clicked.connect(lambda: [
            self.startSearch(),
            self.sync_completer_texts(self.lineEdit.text())
        ])
        self.lineEdit.buttonStop.clicked.connect(self.stopSearch)
        # self.lineEdit.completer().popup().show()
        # self.lineEdit.clicked.connect(lambda : self.lineEdit.completer().popup())
        self.dict_ncbi_headers = {
            "GBSeq_accession-version": "ID",
            "GBSeq_definition": "Description",
            "GBSeq_organism": "Organism",
            "GBSeq_length": "Length",
            "GBSeq_update-date": "Update date",
            "GBSeq_taxonomy": "Taxonomy",
            "GBSeq_create-date": "Create date",
            "GBSeq_moltype": "Molecular type",
            "GBSeq_topology": "Topology",
            "GBSeq_references": "References",
            "GBSeq_source": "Source",
            "GBSeq_keywords": "Keywords",
            "GBSeq_project": "Project",
            "GBSeq_other-seqids": "Other IDs",
            "GBSeq_strandedness": "Strandedness",
            "GBSeq_comment": "Comments"
        }
        self.init_list()
        self.updateSig.connect(self.updateTable)
        self.searchSig.connect(self.ctrlSearchState)
        self.progressBarSig.connect(self.searchProgress)
        self.progressDiologSig.connect(self.runProgressDialog)
        self.spinBox.valueChanged.connect(self.sync_display_items)
        self.ctrlItemsSig.connect(self.ctrlItems)
        # self.downloadFinished.connect(self.downloadDone)
        self.fastaDownloadFinishedSig.connect(self.parent.setTreeViewFocus)
        self.NCBI_model = MyNCBITableModel([self.list_table_header],
                                           parent=self)
        self.tableView.setModel(self.NCBI_model)
        header = CheckBoxHeader(parent=self.tableView)
        header.clicked.connect(self.check_displayed)
        self.tableView.setHorizontalHeader(header)
        self.NCBI_model.checkedChanged.connect(self.ctrl_text)
        self.interrupt = False
        ##下载的状态
        widget = QWidget(self)
        horizBox = QHBoxLayout(widget)
        horizBox.setContentsMargins(0, 0, 0, 0)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        self.disp_check_label = QLabel(
            "<span style='font-weight:600; color:red;'>Searching...</span>",
            self)
        self.disp_check_gifLabel = QLabel(self)
        movie = QMovie(":/picture/resourses/Spinner-1s-34px.gif")
        self.disp_check_gifLabel.setMovie(movie)
        self.disp_check_progress = QProgressBar(self)
        self.disp_check_progress.setFixedWidth(100)
        movie.start()
        horizBox.addItem(spacerItem)
        horizBox.addWidget(self.disp_check_gifLabel)
        horizBox.addWidget(self.disp_check_label)
        horizBox.addWidget(self.disp_check_progress)
        self.statusbar.addPermanentWidget(widget)
        self.disp_check_label.setVisible(False)
        self.disp_check_gifLabel.setVisible(False)
        self.disp_check_progress.setVisible(False)
        self.lineEdit.installEventFilter(self)
        self.spinBox.installEventFilter(self)
        table_popMenu = QMenu(self)
        table_popMenu.setToolTipsVisible(True)
        OpenID = QAction(QIcon(":/seq_Viewer/resourses/field-Display.png"),
                         "Open in NCBI webpage",
                         self,
                         triggered=self.openID)
        OpenID.setToolTip("Open sequence in NCBI webpage")
        table_popMenu.addAction(OpenID)

        def table_popmenu(qpoint):
            if self.tableView.indexAt(qpoint).isValid():
                table_popMenu.exec_(QCursor.pos())

        self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableView.customContextMenuRequested.connect(table_popmenu)
        # self.tableView.horizontalHeader().resizeSection(1, 300)
        # self.tableView.horizontalHeader().resizeSection(2, 80)
        # ##增加check按钮
        # self.check_all = QCheckBox("Check/Uncheck", self)
        # self.check_all.toggled.connect(self.check_displayed)
        # self.statusbar.addWidget(self.check_all)
        ## brief demo
        self.label_6.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#4-3-3-1-Brief-example"
            )))

    @pyqtSlot()
    def on_toolButton_clicked(self):
        info = '''To make use of NCBI's E-utilities, NCBI requires you to specify your email address with each request.<br> 
In case of excessive usage of the E-utilities, NCBI will attempt to contact a user at the email address provided before blocking access to the E-utilities.'''
        QMessageBox.information(
            self, "Information",
            "<p style='line-height:25px; height:25px'>%s</p>" % info)

    @pyqtSlot()
    def on_toolButton_2_clicked(self):
        #refresh
        self.interrupt = False
        self.exist_base = self.NCBI_model.rowCount(self.tableView)
        if not self.exist_base:
            return
        if hasattr(self,
                   "worker_addition") and self.worker_addition.isRunning():
            return
        if self.display_items > self.exist_base:
            self.worker_addition = WorkThread(self.addition_search,
                                              parent=self)
            self.worker_addition.start()
        # elif self.display_items < self.exist_base:
        #     list_checked = self.NCBI_model.list_checked
        #     array = self.NCBI_model.array[:self.display_items+1]
        #     self.NCBI_model = MyNCBITableModel(array, list_checked, self.tableView)
        #     self.tableView.setModel(self.NCBI_model)
        #     self.tableView.update()

    @pyqtSlot()
    def on_toolButton_3_clicked(self):
        # download
        index = self.comboBox.currentIndex()
        self.outputPath = self.comboBox.itemData(index, role=Qt.ToolTipRole)
        self.rettype = "gb" if self.parent.isWorkFolder(self.outputPath,
                                                        mode="gb") else "fasta"
        if self.rettype == "fasta":
            name, ok = QInputDialog.getText(self,
                                            'Set output file name',
                                            'Output file name:',
                                            text="sequence.fas")
            if ok:
                self.fasta_file_name = name + ".fas" if "." not in name else name
                if os.path.exists(self.outputPath + os.sep +
                                  self.fasta_file_name):
                    reply = QMessageBox.question(
                        self, "Concatenate sequence",
                        "<p style='line-height:25px; height:25px'>The file exists, replace it?</p>",
                        QMessageBox.Yes, QMessageBox.Cancel)
                    if reply == QMessageBox.Cancel:
                        return
            else:
                QMessageBox.information(
                    self, "Information",
                    "<p style='line-height:25px; height:25px'>Download canceled!</p>"
                )
                return
        if self.NCBI_model.arraydata:
            self.interrupt = False
            self.progressDialog = self.factory.myProgressDialog(
                "Please Wait", "Downloading...", parent=self)
            self.progressDialog.show()
            self.worker_download = WorkThread(self.downloadSeq, parent=self)
            self.progressDialog.canceled.connect(lambda: [
                setattr(self, "interrupt", True),
                self.worker_download.stopWork(),
                self.progressDialog.close()
            ])
            self.worker_download.finished.connect(self.downloadDone)
            self.worker_download.start()
        else:
            QMessageBox.information(
                self, "Information",
                "<p style='line-height:25px; height:25px'>Please search first!</p>"
            )

    def downloadSeq(self):
        try:
            checked_ids = self.NCBI_model.list_checked
            # if not checked_ids:
            #     checked_ids = self.NCBI_model.fetchAllIDs()
            batch_size = 20
            count = len(checked_ids) if checked_ids else self.count
            self.download_contents = ""
            for start in range(0, count, batch_size):
                if self.interrupt:
                    return
                end = min(count, start + batch_size)
                print("Going to download record %i to %i" % (start + 1, end))
                if (start + batch_size) > count:
                    batch_size = count - start
                if not checked_ids:
                    #下载所有序列的模式
                    fetch_handle = Entrez.efetch(db=self.database,
                                                 rettype=self.rettype,
                                                 retmode="text",
                                                 retstart=start,
                                                 retmax=batch_size,
                                                 webenv=self.webenv,
                                                 query_key=self.query_key)
                else:
                    fetch_handle = Entrez.efetch(db=self.database,
                                                 rettype=self.rettype,
                                                 retmode="text",
                                                 retstart=start,
                                                 retmax=batch_size,
                                                 id=checked_ids)
                self.download_contents += fetch_handle.read()
                self.progressDiologSig.emit(end * 100 / count)
            # index = self.comboBox.currentIndex()
            # filepath = self.comboBox.itemData(index, role=Qt.ToolTipRole)
            # self.downloadFinished.emit()
        except:
            self.exception_signal.emit(''.join(
                traceback.format_exception(*sys.exc_info())))

    def downloadDone(self):
        self.progressDialog.close()
        QMessageBox.information(
            self, "Download finished",
            "<p style='line-height:25px; height:25px'>Done! Back to home page to view them.</p>"
        )
        if self.rettype == "gb":
            self.inputContentSig.emit(self.download_contents, self.outputPath)
        else:
            with open(self.outputPath + os.sep + self.fasta_file_name,
                      "w",
                      encoding="utf-8") as f:
                f.write(self.download_contents)
            self.fastaDownloadFinishedSig.emit(self.outputPath)

    def startSearch(self):
        if hasattr(self, "worker") and self.worker.isRunning():
            return
        if self.spinBox.value() == 0:
            self.spinBox.setValue(20)
        if self.lineEdit.text():
            self.interrupt = False
            self.NCBI_model.init_table()  #刷新一下table(归零)
            self.worker = WorkThread(self.search, parent=self)
            self.worker.start()
        else:
            QMessageBox.information(
                self, "Search in NCBI",
                "<p style='line-height:25px; height:25px'>Please input keywords first!</p>"
            )

    def guiSave(self):
        # Save geometry
        self.serhNCBI_settings.setValue('size', self.size())
        # self.serhNCBI_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            # if isinstance(obj, QComboBox):
            #     # save combobox selection to registry
            #     text = obj.currentText()
            #     if text:
            #         allItems = [
            #             obj.itemText(i) for i in range(obj.count())]
            #         allItems.remove(text)
            #         sortItems = [text] + allItems
            #         self.serhNCBI_settings.setValue(name, sortItems)
            if isinstance(obj, QLineEdit):
                if name == "lineEdit":
                    self.serhNCBI_settings.setValue(name, self.completer_texts)
            if isinstance(obj, QSpinBox):
                if name == "spinBox":
                    self.serhNCBI_settings.setValue(name, self.display_items)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.serhNCBI_settings.value('size', QSize(1000, 600)))
        self.factory.centerWindow(self)
        # self.move(self.serhNCBI_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox":
                    allItems = self.factory.fetchAllWorkFolders(self.workPath)
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        if self.parent.isWorkFolder(i, mode="gb"):
                            text = "\"%s\" in GenBank_File (GenBank format)" % os.path.basename(
                                i)
                        else:
                            text = "\"%s\" in Other_File (Fasta format)" % os.path.basename(
                                i)
                        item = QStandardItem(text)
                        item.setToolTip(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    # obj.setCurrentText(os.path.basename(self.workPath))
                    if self.parent.isWorkFolder(self.workPath, mode="gb"):
                        obj.setCurrentText(
                            "\"%s\" in GenBank_File (GenBank format)" %
                            os.path.basename(self.workPath))
                    else:
                        obj.setCurrentText(
                            "\"%s\" in Other_File (Fasta format)" %
                            os.path.basename(self.workPath))
            if isinstance(obj, QLineEdit):
                if name == "lineEdit":
                    self.completer_texts = self.serhNCBI_settings.value(
                        name, [])  # get stored value from registry
                    self.setLCompleter()
            if isinstance(obj, QSpinBox):
                if name == "spinBox":
                    self.display_items = int(
                        self.serhNCBI_settings.value(
                            name, 100))  # get stored value from registry
                    obj.setValue(self.display_items)

    def runProgressDialog(self, num):
        oldValue = self.progressDialog.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressDialog.setProperty("value", done_int)
            QCoreApplication.processEvents()
            if done_int == 100:
                self.progressDialog.close()

    def popupException(self, exception):
        if ("getaddrinfo failed" in exception) or ("RuntimeError"
                                                   in exception):
            text = "<p style='line-height:25px; height:25px'>Search failed, try to check your network connection!</p>"
        else:
            text = "<p style='line-height:25px; height:25px'>The search failed for some of the sequences. As this may have been  caused by a network problem, you can wait for a while and try again.<br> If the problem persists you may report it at " \
                   "<a href=\"https://github.com/dongzhang0725/PhyloSuite/issues\">https://github.com/dongzhang0725/PhyloSuite/issues</a> " \
                   "or send an email with the detailed traceback to [email protected]</p>"
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(text)
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.interrupt = True
        if hasattr(self, "worker") and self.worker.isRunning():
            self.worker.stopWork()
        if hasattr(self,
                   "worker_addition") and self.worker_addition.isRunning():
            self.worker_addition.stopWork()
        if hasattr(self,
                   "worker_download") and self.worker_download.isRunning():
            self.worker_download.stopWork()
        if hasattr(self, "progressDialog"):
            self.progressDialog.close()
        self.guiSave()
        # self.log_gui.close()  # 关闭子窗口

    def eventFilter(self, obj, event):
        name = obj.objectName()
        if event.type() == QEvent.KeyPress:  # 首先得判断type
            if event.key() == Qt.Key_Return:
                if name == "lineEdit":
                    self.sync_completer_texts(self.lineEdit.text())
                    self.startSearch()
                    return True
                if name == "spinBox":
                    self.on_toolButton_2_clicked()
                    return True
        # return QMainWindow.eventFilter(self, obj, event) #
        # 其他情况会返回系统默认的事件处理方法。
        return super(SerhNCBI, self).eventFilter(obj, event)  # 0

    def sync_completer_texts(self, text):
        if text and (text not in self.completer_texts):
            self.completer_texts.insert(0, text)
        if len(self.completer_texts) > 15:
            self.completer_texts = self.completer_texts[:15]
        self.guiSave()
        self.setLCompleter()

    def setLCompleter(self):
        comp = QCompleter(self.completer_texts)
        comp.setFilterMode(Qt.MatchContains)
        comp.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        # comp.popup().setStyleSheet("background-color: yellow")
        self.lineEdit.setCompleter(comp)

    def init_list(self):
        self.list_table_header = [
            "ID", "Description", "Organism", "Length", "Update date",
            "Taxonomy", "Create date", "Molecular type", "Topology",
            "References", "Source", "Keywords", "Project", "Other IDs",
            "Strandedness", "Comments"
        ]

    def updateTable(self, list_):
        # print(array)
        # 测试array是新添加的list
        self.NCBI_model.appendTable(list_)

    def ctrlSearchState(self, state):
        if state == "searching":
            self.disp_check_label.setText("Searching...")
            self.disp_check_label.setVisible(True)
            self.disp_check_gifLabel.setVisible(True)
            self.lineEdit.buttonSearch.setDisabled(True)
            self.lineEdit.buttonStop.setDisabled(False)
            self.toolButton_2.setDisabled(True)
            self.toolButton_3.setDisabled(True)
        elif state == "fetching":
            self.disp_check_label.setText("Fetching...")
            self.disp_check_label.setVisible(True)
            self.disp_check_gifLabel.setVisible(True)
            self.disp_check_progress.setValue(0)
            self.disp_check_progress.setVisible(True)
            self.lineEdit.buttonStop.setDisabled(False)
        elif state == "except":
            self.disp_check_label.setVisible(False)
            self.disp_check_gifLabel.setVisible(False)
            self.disp_check_progress.setVisible(False)
            self.lineEdit.buttonSearch.setDisabled(False)
            self.lineEdit.buttonStop.setDisabled(True)
            self.toolButton_2.setDisabled(False)
            self.toolButton_3.setDisabled(False)
        elif state == "finished":
            self.disp_check_label.setVisible(False)
            self.disp_check_gifLabel.setVisible(False)
            self.disp_check_progress.setVisible(False)
            self.lineEdit.buttonSearch.setDisabled(False)
            self.lineEdit.buttonStop.setDisabled(True)
            self.toolButton_2.setDisabled(False)
            self.toolButton_3.setDisabled(False)
            ##如果啥也没搜到
            self.exist_base = self.NCBI_model.rowCount(self.tableView)
            if not self.exist_base:
                QMessageBox.information(
                    self, "Download finished",
                    "<p style='line-height:25px; height:25px'>No items found!</p>"
                )

    def ctrlItems(self, count):
        count = count if count != 0 else 20
        self.spinBox.setMaximum(count)
        if count == 0:
            self.spinBox.setValue(20)
        self.label_4.setText("items of %s (total)" % count)
        self.label_2.setText("Download all (%s) sequences to:" % count)
        # if count < self.display_items:
        #     self.display_items = count
        #     self.spinBox.setValue(count)

    def searchProgress(self, value):
        oldValue = self.disp_check_progress.value()
        done_int = int(value)
        if done_int > oldValue:
            self.disp_check_progress.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def sync_display_items(self, value):
        self.display_items = value

    def search(self):
        try:
            self.searchSig.emit("searching")
            self.init_list()
            self.ctrl_text()  #文本还原
            self.NCBI_model.list_checked = []
            self.database = self.comboBox_2.currentText()
            keywords = self.lineEdit.text()
            email = self.lineEdit_2.text()
            email = email if email else "*****@*****.**"
            Entrez.email = email
            search_handle = Entrez.esearch(db=self.database,
                                           term=keywords,
                                           usehistory="y")
            search_results = Entrez.read(search_handle)
            self.webenv = search_results["WebEnv"]
            self.query_key = search_results["QueryKey"]
            self.count = int(search_results["Count"])
            self.ctrlItemsSig.emit(
                self.count)  #如果只有2个序列,self.display_items也会变成2
            search_handle.close()
            batch_size = 20
            self.searchSig.emit("fetching")
            # time_start = time.time()
            total_displayed = self.display_items
            if self.count < total_displayed:
                total_displayed = self.count
            for start in range(0, total_displayed, batch_size):
                # try:
                if self.interrupt:
                    return
                end = min(total_displayed, start + batch_size)
                print("Going to download record %i to %i" % (start + 1, end))
                if (start + batch_size) > total_displayed:
                    batch_size = total_displayed - start
                fetch_handle = Entrez.efetch(db=self.database,
                                             retmode="xml",
                                             retstart=start,
                                             retmax=batch_size,
                                             webenv=self.webenv,
                                             query_key=self.query_key)
                fetch_records = Entrez.read(fetch_handle)
                for num, record in enumerate(fetch_records):
                    list_ = []
                    for i in [
                            "GBSeq_accession-version", "GBSeq_definition",
                            "GBSeq_organism", "GBSeq_length",
                            "GBSeq_update-date", "GBSeq_taxonomy",
                            "GBSeq_create-date", "GBSeq_moltype",
                            "GBSeq_topology", "GBSeq_references",
                            "GBSeq_source", "GBSeq_keywords", "GBSeq_project",
                            "GBSeq_other-seqids", "GBSeq_strandedness",
                            "GBSeq_comment"
                    ]:
                        if i in record:
                            list_.append(str(record[i]))
                        else:
                            list_.append("N/A")
                    self.updateSig.emit(list_)
                    self.progressBarSig.emit(
                        (start + num + 1) * 100 / total_displayed)
                fetch_handle.close()
                # except:
                #     pass
            self.searchSig.emit("finished")
        except:
            self.searchSig.emit("except")
            self.exception_signal.emit(''.join(
                traceback.format_exception(*sys.exc_info())))
        # time_end = time.time()
        # print("time:", time_end - time_start)

    def addition_search(self):
        try:
            total_displayed = self.display_items
            if self.count < total_displayed:
                total_displayed = self.count
            batch_size = 20
            self.searchSig.emit("fetching")
            for start in range(self.exist_base, total_displayed, batch_size):
                if self.interrupt:
                    break
                end = min(total_displayed, start + batch_size)
                print("Going to download record %i to %i" % (start + 1, end))
                if (start + batch_size) > total_displayed:
                    batch_size = total_displayed - start
                fetch_handle = Entrez.efetch(db=self.database,
                                             retmode="xml",
                                             retstart=start,
                                             retmax=batch_size,
                                             webenv=self.webenv,
                                             query_key=self.query_key)
                fetch_records = Entrez.read(fetch_handle)
                for num, record in enumerate(fetch_records):
                    list_ = []
                    for i in [
                            "GBSeq_accession-version", "GBSeq_definition",
                            "GBSeq_organism", "GBSeq_length",
                            "GBSeq_update-date", "GBSeq_taxonomy",
                            "GBSeq_create-date", "GBSeq_moltype",
                            "GBSeq_topology", "GBSeq_references",
                            "GBSeq_source", "GBSeq_keywords", "GBSeq_project",
                            "GBSeq_other-seqids", "GBSeq_strandedness",
                            "GBSeq_comment"
                    ]:
                        if i in record:
                            list_.append(str(record[i]))
                        else:
                            list_.append("N/A")
                    self.updateSig.emit(list_)
                    self.progressBarSig.emit(
                        (start - self.exist_base + num + 1) * 100 /
                        (total_displayed - self.exist_base))
                # self.progressBarSig.emit((start - self.exist_base)*100/(total_displayed - self.exist_base))
                fetch_handle.close()
            self.searchSig.emit("finished")
        except:
            self.searchSig.emit("except")
            self.exception_signal.emit(''.join(
                traceback.format_exception(*sys.exc_info())))

    def ctrl_text(self):
        selected_num = len(self.NCBI_model.list_checked)
        if selected_num:
            self.label_2.setText("Download selected (%d) sequences to:" %
                                 (selected_num))
        else:
            text = "Download all (%d) sequences to:" % self.count if hasattr(
                self, "count") else "Download all sequences to:"
            self.label_2.setText(text)

    def openID(self):
        indices = self.tableView.selectedIndexes()
        arraydata = self.NCBI_model.arraydata
        ID = arraydata[indices[0].row()][0].text()
        url = "https://www.ncbi.nlm.nih.gov/nuccore/%s"%ID if self.comboBox_2.currentText() == "nucleotide" \
            else "https://www.ncbi.nlm.nih.gov/protein/%s"%ID
        QDesktopServices.openUrl(QUrl(url))

    def stopSearch(self):
        self.interrupt = True
        if hasattr(self, "worker") and self.worker.isRunning():
            self.worker.stopWork()
        if hasattr(self,
                   "worker_addition") and self.worker_addition.isRunning():
            self.worker_addition.stopWork()
        self.searchSig.emit("except")

    def check_displayed(self, isCheck):
        if hasattr(self, "NCBI_model") and self.NCBI_model.rowCount(
                self.tableView):
            # self.NCBI_model.beginResetModel()
            self.NCBI_model.layoutAboutToBeChanged.emit()
            if isCheck:
                self.NCBI_model.list_checked = [
                    i[0].text() for i in self.NCBI_model.arraydata
                ]
            else:
                self.NCBI_model.list_checked = []
            self.NCBI_model.layoutChanged.emit()
            self.ctrl_text()
Пример #6
0
class DrawRSCUfig(QDialog, Ui_RSCUfig, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    warning_signal = pyqtSignal(str)
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)

    def __init__(self,
                 autoInputs=None,
                 workPath=None,
                 focusSig=None,
                 RscriptPath=None,
                 parent=None):
        super(DrawRSCUfig, self).__init__(parent)
        self.parent = parent
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig
        self.autoInputs = autoInputs
        self.RscriptPath = RscriptPath
        self.setupUi(self)
        # 保存设置
        self.DrawRSCUfig_settings = QSettings(
            self.thisPath + '/settings/DrawRSCUfig_settings.ini',
            QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.DrawRSCUfig_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.warning_signal.connect(self.popupWarning)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.listWidget_3.installEventFilter(self)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-14-3-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        open files
        """
        fileNames = QFileDialog.getOpenFileNames(self,
                                                 "Input Files",
                                                 filter="CSV Format(*.csv);;")
        if fileNames[0]:
            dict_inputs = OrderedDict()
            for i in fileNames[0]:
                dict_inputs[i] = os.path.splitext(os.path.basename(i))[0]
            self.inputListWidget_3(dict_inputs)

    @pyqtSlot()
    def on_toolButton_T_clicked(self):
        '''
        delete
        '''
        listItems = self.listWidget_3.selectedItems()
        if not listItems:
            return
        for item in listItems:
            self.listWidget_3.takeItem(self.listWidget_3.row(item))

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        '''
        cancel
        '''
        # ctypes.windll.kernel32.TerminateThread(  # @UndefinedVariable
        #     self.worker.handle, 0)
        self.close()

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        dict_inputs = OrderedDict()
        for i in range(self.listWidget_3.count()):
            dict_inputs[self.listWidget_3.item(
                i).toolTip()] = self.listWidget_3.item(i).text()
        if dict_inputs:
            # 有数据才执行
            self.dict_args = {}
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + os.sep +
                                                      "RSCUfig_results" +
                                                      os.sep +
                                                      self.output_dir_name)
            self.dict_args["exportPath"] = self.exportPath
            self.dict_args["dict_files_title"] = dict_inputs
            self.dict_args["exception_signal"] = self.exception_signal
            self.dict_args["progressSig"] = self.progressSig
            xItems = [
                self.listWidget_2.item(i).text()
                for i in range(self.listWidget_2.count())
            ]
            self.dict_args["Order of x-axis"] = xItems
            colorItems = [
                i.text() for i in [
                    self.pushButton_color, self.pushButton_color_2,
                    self.pushButton_color_3, self.pushButton_color_4
                ]
            ]
            self.dict_args["Color of stacks"] = colorItems
            self.dict_args["Figure height"] = self.spinBox_5.value()
            self.dict_args["Figure width"] = self.spinBox_6.value()
            self.dict_args["height proportion"] = self.doubleSpinBox_2.value()
            self.dict_args["ylim"] = self.doubleSpinBox_3.value()
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                #提醒是否删除旧结果,如果用户取消,就不执行
                return
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.critical(
                self, "Draw RSCU figure",
                "<p style='line-height:25px; height:25px'>Please input files first!</p>"
            )

    def run_command(self):
        try:
            # 清空文件夹,放在这里方便统一报错
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start", self.exportPath,
                self.qss_file, self
            ])
            rscriptPath = self.rscu2fig()
            # subprocess.call([self.RscriptPath, "--vanilla", rscriptPath], shell=True)
            subprocess.call("%s --vanilla %s" %
                            (self.RscriptPath, rscriptPath),
                            shell=True)
            self.progressSig.emit(100)
            if not os.path.exists(self.RSCUpath):
                self.warning_signal.emit(
                    "No RSCU figure generated! The R packages \"ggplot2\" or \"ggpubr\" may not be installed properly. You may:<br>"
                    "&nbsp;&nbsp;&nbsp;<span style='font-weight:600'>1</span>. Install \"ggplot2\" and \"ggpubr\" manually, restart PhyloSuite and try again<br>"
                    "&nbsp;&nbsp;&nbsp;<span style='font-weight:600'>2</span>. Execute \"rscu_scripts.r\" script: <span style='font-weight:600; color:#ff0000;'>Rscript rscu_scripts.r</span><br>"
                    "&nbsp;&nbsp;&nbsp;<span style='font-weight:600'>3</span>. Copy the content of \"rscu_scripts.r\" and paste it into Rstudio or Rgui to execute"
                )
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop", self.exportPath,
                    self.qss_file, self
                ])
            if os.path.exists(self.exportPath + os.sep + "Rplots.pdf"):
                os.remove(self.exportPath + os.sep + "Rplots.pdf")
            self.focusSig.emit(self.exportPath)
            time_end = datetime.datetime.now()
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), str(time_end - time_start))
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    "If you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n\n"
                    + self.time_used_des)
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except", self.exportPath,
                self.qss_file, self
            ])

    def guiSave(self):
        # Save geometry
        self.DrawRSCUfig_settings.setValue('size', self.size())
        # self.DrawRSCUfig_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QListWidget):
                if name == "listWidget_3":
                    dict_inputs = OrderedDict()
                    for i in range(obj.count()):
                        dict_inputs[obj.item(i).toolTip()] = obj.item(i).text()
                    self.DrawRSCUfig_settings.setValue(name, dict_inputs)
                else:
                    allItems = [obj.item(i).text() for i in range(obj.count())]
                    self.DrawRSCUfig_settings.setValue(name, allItems)
            if isinstance(obj, QSpinBox):
                value = obj.value()
                self.DrawRSCUfig_settings.setValue(name, value)
            if isinstance(obj, QDoubleSpinBox):
                value = obj.value()
                self.DrawRSCUfig_settings.setValue(name, value)
            if isinstance(obj, QPushButton):
                if name in [
                        "pushButton_color", "pushButton_color_2",
                        "pushButton_color_3", "pushButton_color_4"
                ]:
                    color = obj.palette().color(1)
                    self.DrawRSCUfig_settings.setValue(name, color.name())

    def guiRestore(self):

        # Restore geometry
        self.resize(self.DrawRSCUfig_settings.value('size', QSize(500, 500)))
        self.factory.centerWindow(self)
        # self.move(self.DrawRSCUfig_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QListWidget):
                if name == "listWidget_2":
                    ini_list = [
                        "Gln", "His", "Asn", "Pro", "Thr", "Leu1", "Glu",
                        "Met", "Arg", "Tyr", "Asp", "Lys", "Ala", "Ile",
                        "Ser1", "Ser2", "Leu2", "Cys", "Trp", "Val", "Gly",
                        "Phe"
                    ]
                    values = self.DrawRSCUfig_settings.value(name, ini_list)
                    self.inputListWidget(values, obj)
                elif name == "listWidget_3":
                    ini_dict_input = OrderedDict()
                    dict_inputs = self.DrawRSCUfig_settings.value(
                        name, ini_dict_input)
                    if self.autoInputs:
                        dict_inputs = OrderedDict()
                        for i in self.autoInputs:
                            dict_inputs[i] = os.path.splitext(
                                os.path.basename(i))[0]
                    self.inputListWidget_3(dict_inputs)
            if isinstance(obj, QSpinBox):
                value = self.DrawRSCUfig_settings.value(name, 8)
                obj.setValue(int(value))
            if isinstance(obj, QDoubleSpinBox):
                value = self.DrawRSCUfig_settings.value(name, None)
                if value:
                    obj.setValue(float(value))
            if isinstance(obj, QPushButton):
                if obj in [
                        self.pushButton_color, self.pushButton_color_2,
                        self.pushButton_color_3, self.pushButton_color_4
                ]:
                    dict_ini_colors = {
                        "pushButton_color": "#6598C9",
                        "pushButton_color_2": "#CB4A28",
                        "pushButton_color_3": "#9AC664",
                        "pushButton_color_4": "#7F5499"
                    }
                    ini_color = dict_ini_colors[name]
                    color = self.DrawRSCUfig_settings.value(name, ini_color)
                    obj.setStyleSheet("background-color:%s" % color)
                    obj.setText(color)
                    obj.clicked.connect(self.changePbColor)

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        rgx = re.compile(r'Permission.+?[\'\"](.+\.pdf)[\'\"]')
        if rgx.search(exception):
            pdffile = rgx.search(exception).group(1)
            reply = QMessageBox.critical(
                self, "Draw RSCU figure",
                "<p style='line-height:25px; height:25px'>Please close 'pdf' file first!</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes and platform.system().lower(
            ) == "windows":
                os.startfile(pdffile)
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
            )
            msg.setWindowTitle("Error")
            msg.setDetailedText(exception)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    def closeEvent(self, event):
        self.guiSave()

    def inputListWidget(self, items, listwidget):
        listwidget.clear()
        for num, i in enumerate(items):
            item = QListWidgetItem(i)
            # 背景颜色
            if num % 2 == 0:
                item.setBackground(QColor(255, 255, 255))
            else:
                item.setBackground(QColor(237, 243, 254))
            listwidget.addItem(item)

    def inputListWidget_3(self, dict_items):
        if not dict_items:
            return
        self.listWidget_3.clear()
        for num, i in enumerate(dict_items):
            if os.path.exists(i):
                item = QListWidgetItem(dict_items[i])
                item.setToolTip(i)
                item.setFlags(item.flags() | Qt.ItemIsEditable)
                # 背景颜色
                if num % 2 == 0:
                    item.setBackground(QColor(255, 255, 255))
                else:
                    item.setBackground(QColor(237, 243, 254))
                self.listWidget_3.addItem(item)

    def rscu2fig(self):
        script = '''# auto install missing packages
list.of.packages <- c("ggplot2", "ggpubr")
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages)) install.packages(new.packages, repos="http://cran.us.r-project.org")

library("ggplot2")
scaleFUN <- function(x) sprintf("%.2f", x)  #可以设置坐标轴显示的小数点位数,为了与图注保持一致\n'''
        allfig = []  # [1,2,3,4]
        sums = len(self.dict_args["dict_files_title"])
        for num, i in enumerate(self.dict_args["dict_files_title"]):
            self.Order_name = self.factory.int2word(num + 1).strip().replace(
                " ", "_")
            self.rscu_file = os.path.normpath(i).replace("\\", "/")
            self.latin_name = self.dict_args["dict_files_title"][i]
            self.spe_number = str(num + 1)
            allfig.append(self.spe_number)
            self.Xaxis = '"' + '","'.join(
                self.dict_args["Order of x-axis"]) + '"'
            self.ylim = "%.1f" % self.dict_args["ylim"]
            # 生成RSCU堆积条形图代码
            script_i = '''
{self.Order_name} <- read.table("{self.rscu_file}",header = TRUE,sep=",")
f{self.Order_name} <- factor({self.Order_name}$Fill, levels = unique(rev({self.Order_name}$Fill)))   #填充的颜色的factor
#自定义横坐标顺序
x{self.Order_name} <- factor({self.Order_name}$AA, levels=c({self.Xaxis}))  #横坐标
y{self.Order_name} <- {self.Order_name}$RSCU  #Y值
z{self.Order_name} <- {self.Order_name}$Equality  #图注的Y值
l{self.Order_name} <- {self.Order_name}$Codon  #图注打标签
p{self.spe_number} <- ggplot(data = {self.Order_name}, mapping = aes(x = x{self.Order_name}, y = y{self.Order_name}, fill = f{self.Order_name},width = .7)) + geom_bar(stat = 'identity', position = 'stack')+ ggtitle("{self.latin_name}")+theme(axis.title.x=element_blank(),axis.title.y=element_text(size=10),legend.position="none",panel.background=element_blank(),panel.grid.minor=element_blank(),plot.background=element_blank(),axis.line.x = element_line(color="black", size = 0.5),axis.line.y = element_line(color="black", size = 0.5),axis.text.x=element_blank(), plot.title=element_text(family="sans",face="italic",hjust=0,size=10)) + scale_fill_manual("legend", values = c("1" = "{self.dict_args[Color of stacks][0]}", "2" = "{self.dict_args[Color of stacks][1]}", "3" = "{self.dict_args[Color of stacks][2]}", "4" = "{self.dict_args[Color of stacks][3]}"))+geom_text(mapping = aes(label = factor({self.Order_name}$aaRatio)), size = 2.5, vjust=-.2,position = position_stack()) + ylim(0,{self.ylim})+ylab("RSCU")\n'''.format(
                self=self)
            if num == (len(self.dict_args["dict_files_title"]) - 1):
                #最后一个要添加横坐标
                script_i = script_i.replace("axis.text.x=element_blank(),", "")
            script += script_i
            if num == 0:
                bottom = '''
p <- ggplot(data = {self.Order_name}, mapping = aes(x = x{self.Order_name}, y = z{self.Order_name}, fill = f{self.Order_name},width = .9)) + geom_bar(stat = 'identity', position = 'stack') + geom_text(mapping = aes(label = l{self.Order_name}), size = 2.4, colour = 'white', position = position_stack(vjust=.5))+theme(axis.text.x=element_blank(),axis.text.y=element_blank(),axis.ticks=element_blank(),axis.title.x=element_blank(),axis.title.y=element_blank(),legend.position="none",panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),panel.grid.minor=element_blank(),plot.background=element_blank(),axis.line.y = element_blank()) + scale_fill_manual("legend", values = c("1" = "{self.dict_args[Color of stacks][0]}", "2" = "{self.dict_args[Color of stacks][1]}", "3" = "{self.dict_args[Color of stacks][2]}", "4" = "{self.dict_args[Color of stacks][3]}"))\n'''.format(
                    self=self)
            self.progressSig.emit((num + 1) * 50 / sums)
        script += bottom
        allfig.append("")
        self.allfignum = "p" + ",p".join(allfig)
        self.str_matrix = ",".join(
            ["1"] * len(self.dict_args["dict_files_title"]) +
            ["%.2f" %
             (1 / self.dict_args["height proportion"])])  # (1,1,1,0.5)
        self.nrow = str(len(self.dict_args["dict_files_title"]) + 1)
        self.RSCUpath = os.path.normpath(self.exportPath + os.sep +
                                         "RSCU.pdf").replace("\\", "/")
        script += '''
library("ggpubr")
pall <- ggarrange({self.allfignum}, heights=c({self.str_matrix}), ncol=1, nrow={self.nrow}, align ="v")
pdf("{self.RSCUpath}",width={self.dict_args[Figure width]},height={self.dict_args[Figure height]}) ## 如果觉得比例不合适,可以适当调整width和height的大小。
pall
dev.off() 
'''.format(self=self)
        scriptPath = self.exportPath + os.sep + "rscu_scripts.r"
        with open(scriptPath, "w", encoding="utf-8") as f:
            f.write(script)
        self.progressSig.emit(60)
        return scriptPath

    def changePbColor(self):
        button = self.sender()
        ini_color = button.palette().color(1)
        color = QColorDialog.getColor(QColor(ini_color), self)
        if color.isValid():
            button.setText(color.name())
            button.setStyleSheet("background-color:%s" % color.name())

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QListWidget):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                dict_inputs = {}
                for i in files:
                    dict_inputs[i] = os.path.splitext(os.path.basename(i))[0]
                self.inputListWidget_3(dict_inputs)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # 其他情况会返回系统默认的事件处理方法。
        return super(DrawRSCUfig, self).eventFilter(obj, event)  # 0

    def popupWarning(self, text):
        QMessageBox.warning(
            self, "Warning",
            "<p style='line-height:25px; height:25px'>%s</p>" % text)
Пример #7
0
class CompareTable(QDialog, Ui_compareTable, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)

    def __init__(self,
                 autoInputs=None,
                 workPath=None,
                 focusSig=None,
                 MAFFTpath=None,
                 parent=None):
        super(CompareTable, self).__init__(parent)
        self.parent = parent
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.mafft_exe = MAFFTpath
        self.setupUi(self)
        self.autoInputs = autoInputs
        self.focusSig = focusSig
        # 保存设置
        self.CompareTable_settings = QSettings(
            self.thisPath + '/settings/CompareTable_settings.ini',
            QSettings.IniFormat)
        # File only, no fallback to registry or or.
        self.CompareTable_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.listWidget.installEventFilter(self)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-14-2-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        self.list_files = [
            self.listWidget.item(i).toolTip()
            for i in range(self.listWidget.count())
        ]
        if len(self.list_files) >= 2:
            # 有数据才执行
            self.headerRows = self.spinBox.value()
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + os.sep +
                                                      "comp_tbl_results" +
                                                      os.sep +
                                                      self.output_dir_name)
            self.ispairwiseAlign = self.checkBox.isChecked()
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                #提醒是否删除旧结果,如果用户取消,就不执行
                return
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.critical(
                self, "Compare table",
                "<p style='line-height:25px; height:25px'>Please input at least two files!</p>"
            )

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        open files
        """
        fileNames = QFileDialog.getOpenFileNames(self,
                                                 "Input Files",
                                                 filter="CSV Format(*.csv);;")
        if fileNames[0]:
            self.input(fileNames[0])

    @pyqtSlot()
    def on_toolButton_T_clicked(self):
        '''
        delete
        '''
        listItems = self.listWidget.selectedItems()
        if not listItems:
            return
        for item in listItems:
            self.listWidget.takeItem(self.listWidget.row(item))

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        '''
        cancel
        '''
        # ctypes.windll.kernel32.TerminateThread(  # @UndefinedVariable
        #     self.worker.handle, 0)

        self.close()

    def run_command(self):
        try:
            #清空文件夹,放在这里方便统一报错
            time_start = datetime.datetime.now()
            # raise BaseException
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start", self.exportPath,
                self.qss_file, self
            ])
            self.gatherTable = GatherTable(self.list_files,
                                           self.ispairwiseAlign,
                                           self.exportPath, self.progressSig,
                                           self.mafft_exe, self.headerRows)
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "stop", self.exportPath,
                self.qss_file, self
            ])
            self.focusSig.emit(self.exportPath)
            time_end = datetime.datetime.now()
            description = """The sequences were pairwise aligned with MAFFT (Katoh and Standley, 2013) first, then the genetic distances (identity) among sequences were calculated with the “DistanceCalculator” function in Biopython (C**k, et al., 2009) using the “identity” model."""
            mafft_ref = "Katoh, K., Standley, D.M., 2013. MAFFT multiple sequence alignment software version 7: improvements in performance and usability. Mol. Biol. Evol. 30, 772-780."
            biopython_ref = "C**k, P.J., Antao, T., Chang, J.T., Chapman, B.A., Cox, C.J., Dalke, A., Friedberg, I., Hamelryck, T., Kauff, F., Wilczynski, B., et al. (2009). Biopython: freely available Python tools for computational molecular biology and bioinformatics. Bioinformatics 25, 1422-1423."
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), str(time_end - time_start))
            ps_cite = "If you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n\n"
            text = ps_cite + self.time_used_des if not self.checkBox.isChecked(
            ) else description + "\n\n" + ps_cite + "If you use MAFFT, please cite:\n%s\n\nIf you use Biopython, please cite:\n%s\n\n" % (
                mafft_ref, biopython_ref) + self.time_used_des
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(text)
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except", self.exportPath,
                self.qss_file, self
            ])

    def guiSave(self):
        # Save geometry
        self.CompareTable_settings.setValue('size', self.size())
        # self.CompareTable_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QListWidget):
                allItems = [obj.item(i).toolTip() for i in range(obj.count())]
                self.CompareTable_settings.setValue(name, allItems)
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.CompareTable_settings.setValue(name, state)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.CompareTable_settings.value('size', QSize(500, 500)))
        # self.move(self.CompareTable_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QListWidget):
                if self.autoInputs:
                    self.input(self.autoInputs)
                else:
                    values = self.CompareTable_settings.value(name, [])
                    if values:
                        self.input(values)
            if isinstance(obj, QCheckBox):
                value = self.CompareTable_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        rgx = re.compile(r'Permission.+?[\'\"](.+\.csv)[\'\"]')
        if rgx.search(exception):
            csvfile = rgx.search(exception).group(1)
            reply = QMessageBox.critical(
                self, "Compare table",
                "<p style='line-height:25px; height:25px'>Please close '%s' file first!</p>"
                % os.path.basename(csvfile), QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes and platform.system().lower(
            ) == "windows":
                os.startfile(csvfile)
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
            )
            msg.setWindowTitle("Error")
            msg.setDetailedText(exception)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    def closeEvent(self, event):
        self.guiSave()

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QListWidget):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                self.input(files)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # 其他情况会返回系统默认的事件处理方法。
        return super(CompareTable, self).eventFilter(obj, event)  # 0

    def input(self, files):
        self.listWidget.clear()
        for num, i in enumerate(files):
            if not os.path.exists(i):
                continue
            item = QListWidgetItem(os.path.basename(i))
            item.setToolTip(i)
            # 背景颜色
            if num % 2 == 0:
                item.setBackground(QColor(255, 255, 255))
            else:
                item.setBackground(QColor(237, 243, 254))
            self.listWidget.addItem(item)
Пример #8
0
class HmmCleaner(QDialog, Ui_HmmCleaner, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    logGuiSig = pyqtSignal(str)
    HmmCleaner_exception = pyqtSignal(str)
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    # 用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)
    # 用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)

    def __init__(self,
                 workPath=None,
                 HmmCleanerPath=None,
                 autoInputs=None,
                 perl=None,
                 focusSig=None,
                 workflow=None,
                 parent=None):
        super(HmmCleaner, self).__init__(parent)
        self.parent = parent
        self.workflow = workflow
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig
        self.autoInputs = autoInputs
        self.HmmCleanerPath = HmmCleanerPath
        self.perl = perl
        self.setupUi(self)
        # 保存设置
        if not workflow:
            self.HmmCleaner_settings = QSettings(
                self.thisPath + '/settings/HmmCleaner_settings.ini',
                QSettings.IniFormat)
        else:
            self.HmmCleaner_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini',
                QSettings.IniFormat)
            self.HmmCleaner_settings.beginGroup("Workflow")
            self.HmmCleaner_settings.beginGroup("temporary")
            self.HmmCleaner_settings.beginGroup('HmmCleaner')
        # File only, no fallback to registry or or.
        self.HmmCleaner_settings.setFallbacksEnabled(False)
        # print(self.HmmCleaner_settings.childGroups())
        # self.factory.settingsGroup2Group(self.HmmCleaner_settings, "PCGs", "temporary")
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.interrupt = False
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.logGuiSig.connect(self.addText2Log)
        self.comboBox_4.installEventFilter(self)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  # 自动识别可用的输入
        self.log_gui = self.gui4Log()
        self.HmmCleaner_exception.connect(self.popup_HmmCleaner_exception)
        self.checkBox_4.toggled.connect(self.popupAliWarning)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        action = QAction(QIcon(":/picture/resourses/terminal-512.png"),
                         "View | Edit command",
                         menu,
                         triggered=self.showCMD)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(action)
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_7.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-5-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        self.command = self.fetchCommands()
        if self.command:
            self.interrupt = False
            self.error_has_shown = False  # 保证只报一次错
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            thread = thread if len(
                self.dict_args["inputFiles"]) > thread else len(
                    self.dict_args["inputFiles"])
            thread = 1 if not self.dict_args[
                "inputFiles"] else thread  # compare的情况
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        show log
        """
        self.log_gui.show()

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        alignment file
        """
        fileNames = QFileDialog.getOpenFileNames(self, "Input alignment file")
        if fileNames[0]:
            self.input(fileNames[0])

    @pyqtSlot()
    def on_pushButton_2_clicked(self, quiet=False):
        """
        Stop
        """
        if self.isRunning():
            if (not self.workflow) and (not quiet):
                reply = QMessageBox.question(
                    self, "Confirmation",
                    "<p style='line-height:25px; height:25px'>HmmCleaner is still running, terminate it?</p>",
                    QMessageBox.Yes, QMessageBox.Cancel)
            else:
                reply = QMessageBox.Yes
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
                if (not self.workflow) and (not quiet):
                    QMessageBox.information(
                        self, "HmmCleaner",
                        "<p style='line-height:25px; height:25px'>Program has been terminated!</p>"
                    )
                self.startButtonStatusSig.emit([
                    self.pushButton, [self.progressBar], "except",
                    self.dict_args["exportPath"], self.qss_file, self
                ])

    def run_command(self):
        try:
            # 清空文件夹,放在这里方便统一报错
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            ##进度条用
            # self.dict_file_progress = {os.path.basename(file): 0 for file in self.dict_args["seq_files"]}
            async_results = [
                self.pool.apply_async(run,
                                      args=(self.dict_args, self.command,
                                            file))
                for file in self.dict_args["inputFiles"]
            ]
            self.totalFileNum = len(self.dict_args["inputFiles"])
            self.finishedFileNum = 0  #进度条用
            self.pool.close()  # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
            map(ApplyResult.wait, async_results)
            lst_results = [r.get() for r in async_results]
            # 判断比对是否成功
            HmmCleaner_results = glob.glob(self.exportPath + os.sep +
                                           "*_hmm.*")
            empty_files = [
                os.path.basename(file) for file in HmmCleaner_results
                if os.stat(file).st_size == 0
            ]
            has_error = False
            if (not self.checkBox_3.isChecked()) and (not HmmCleaner_results
                                                      or empty_files):
                # log_only不管
                has_error = True
                log = self.textEdit_log.toPlainText()
                if "Can't locate" in log:
                    self.HmmCleaner_exception.emit(
                        "HmmCleaner executes failed, it seems the "
                        "<a href=\"https://cpandeps.grinnz.com/?dist=Bio-MUST-Apps-HmmCleaner&phase=build&perl_version=v5.30.0&style=table\">"
                        "<span style=\" font-size:12pt; text-decoration: underline; color:#0000ff;\">dependencies"
                        "</span></a>(e.g. Bio-FastParsers) of HmmCleaner are not installed, "
                        "click <span style=\"color:red\">Show log</span> to see details! <br> You can install HmmCleaner following this " \
                   "<a href=\"https://dongzhang0725.github.io/dongzhang0725.github.io/PhyloSuite-demo/how-to-configure-plugins/#2-4-HmmCleaner-configuration\">" \
                   "<span style=\" font-size:12pt; text-decoration: underline; color:#0000ff;\">instruction</a>." \
                   "</span>")
                else:
                    list_commands = re.findall(r"Command: (.+)\n", log)
                    last_cmd = list_commands[-1] if list_commands else ""
                    self.HmmCleaner_exception.emit(
                        "HmmCleaner executes failed, click <span style=\"color:red\">Show log</span> to see details! "
                        "You can also copy this command to terminal to debug: %s"
                        % last_cmd)
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), self.time_used)
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    self.description +
                    "\n\nIf you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n"
                    "If you use HmmCleaner, please cite:\n" + self.reference +
                    "\n\n" + self.time_used_des)
            if (not self.interrupt) and (not has_error):
                self.pool = None
                self.interrupt = False
                if self.workflow:
                    # work flow跑的
                    self.startButtonStatusSig.emit([
                        self.pushButton, self.progressBar, "workflow stop",
                        self.exportPath, self.qss_file, self
                    ])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop", self.exportPath,
                    self.qss_file, self
                ])
                self.focusSig.emit(self.exportPath)
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])
                self.pool = None
                self.interrupt = False
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            self.pool = None
            self.interrupt = False

    def guiSave(self):
        # Save geometry
        self.HmmCleaner_settings.setValue('size', self.size())
        # self.HmmCleaner_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                index = obj.currentIndex()
                self.HmmCleaner_settings.setValue(name, index)
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.HmmCleaner_settings.setValue(name, state)
            elif isinstance(obj, QDoubleSpinBox):
                float_ = obj.value()
                self.HmmCleaner_settings.setValue(name, float_)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.HmmCleaner_settings.value('size', QSize(490, 380)))
        # self.move(self.HmmCleaner_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox_6":
                    cpu_num = multiprocessing.cpu_count()
                    list_cpu = [str(i + 1) for i in range(cpu_num)]
                    index = self.HmmCleaner_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(list_cpu):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
                elif name == "comboBox_4":
                    self.input(self.autoInputs)
                else:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    index = self.HmmCleaner_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        item.setToolTip(i)
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
            elif isinstance(obj, QCheckBox):
                value = self.HmmCleaner_settings.value(
                    name, "no setting")  # get stored value from registry
                if value != "no setting":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            elif isinstance(obj, QDoubleSpinBox):
                ini_float_ = obj.value()
                float_ = self.HmmCleaner_settings.value(name, ini_float_)
                obj.setValue(float(float_))

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.guiSave()
        self.log_gui.close()  # 关闭子窗口
        self.closeSig.emit("HmmCleaner", self.fetchWorkflowSetting())
        # 断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("HmmCleaner")
            # 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            reply = QMessageBox.question(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>HmmCleaner is still running, terminate it?</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
            else:
                event.ignore()

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                self.input(files)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # return QMainWindow.eventFilter(self, obj, event) #
        # 其他情况会返回系统默认的事件处理方法。
        return super(HmmCleaner, self).eventFilter(obj, event)  # 0

    def gui4Log(self):
        dialog = QDialog(self)
        dialog.resize(800, 500)
        dialog.setWindowTitle("Log")
        gridLayout = QGridLayout(dialog)
        horizontalLayout_2 = QHBoxLayout()
        label = QLabel(dialog)
        label.setText("Log of HmmCleaner:")
        horizontalLayout_2.addWidget(label)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        horizontalLayout_2.addItem(spacerItem)
        toolButton = QToolButton(dialog)
        icon2 = QIcon()
        icon2.addPixmap(
            QPixmap(
                ":/picture/resourses/interface-controls-text-wrap-512.png"))
        toolButton.setIcon(icon2)
        toolButton.setCheckable(True)
        toolButton.setToolTip("Use Wraps")
        toolButton.clicked.connect(self.setWordWrap)
        toolButton.setChecked(True)
        horizontalLayout_2.addWidget(toolButton)
        pushButton = QPushButton("Save to file", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
        pushButton.setIcon(icon)
        pushButton_2 = QPushButton("Close", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png"))
        pushButton_2.setIcon(icon)
        self.textEdit_log = QTextEdit(dialog)
        self.textEdit_log.setReadOnly(True)
        gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2)
        gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2)
        gridLayout.addWidget(pushButton, 2, 0, 1, 1)
        gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
        pushButton.clicked.connect(self.save_log_to_file)
        pushButton_2.clicked.connect(dialog.close)
        dialog.setWindowFlags(dialog.windowFlags()
                              | Qt.WindowMinMaxButtonsHint)
        return dialog

    def addText2Log(self, text):
        if re.search(r"\w+", text):
            self.textEdit_log.append(text)

    def save_log_to_file(self):
        content = self.textEdit_log.toPlainText()
        fileName = QFileDialog.getSaveFileName(self, "HmmCleaner", "log",
                                               "text Format(*.txt)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(content)

    def setWordWrap(self):
        button = self.sender()
        if button.isChecked():
            button.setChecked(True)
            self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            button.setChecked(False)
            self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap)

    def input(self, list_items=None):
        if list_items:
            self.comboBox_4.refreshInputs(list_items)
        else:
            self.comboBox_4.refreshInputs([])

    def showCMD(self):
        """
        show command
        """
        self.command = self.fetchCommands()
        if self.command:
            dialog = QDialog(self)
            dialog.resize(600, 200)
            dialog.setWindowTitle("Command")
            gridLayout = QGridLayout(dialog)
            label = QLabel(dialog)
            label.setText("Current Command:")
            pushButton = QPushButton("Save and run", dialog)
            icon = QIcon()
            icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
            pushButton.setIcon(icon)
            pushButton_2 = QPushButton("Close", dialog)
            icon = QIcon()
            icon.addPixmap(
                QPixmap(":/picture/resourses/if_Delete_1493279.png"))
            pushButton_2.setIcon(icon)
            self.textEdit_cmd = QTextEdit(dialog)
            self.textEdit_cmd.setText(self.command)
            self.textEdit_cmd.textChanged.connect(self.judgeCmdText)
            gridLayout.addWidget(label, 0, 0, 1, 2)
            gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2)
            gridLayout.addWidget(pushButton, 2, 0, 1, 1)
            gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
            pushButton.clicked.connect(lambda: [
                self.run_with_CMD(self.textEdit_cmd.toPlainText()),
                dialog.close()
            ])
            pushButton_2.clicked.connect(dialog.close)
            dialog.setWindowFlags(dialog.windowFlags()
                                  | Qt.WindowMinMaxButtonsHint)
            dialog.exec_()

    def isRunning(self):
        '''判断程序是否运行,依赖进程是否存在来判断'''
        return hasattr(self, "pool") and self.pool and not self.interrupt

    def run_with_CMD(self, cmd):
        self.command = cmd
        if self.command:
            self.interrupt = False
            self.error_has_shown = False
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            thread = thread if len(
                self.dict_args["inputFiles"]) > thread else len(
                    self.dict_args["inputFiles"])
            thread = 1 if not self.dict_args[
                "inputFiles"] else thread  # compare的情况
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    def judgeCmdText(self):
        text = self.textEdit_cmd.toPlainText()
        if " $alignment$" not in text:
            QMessageBox.information(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>\"$alignment$\" cannot be changed!</p>"
            )
            self.textEdit_cmd.undo()

    def fetchCommands(self):
        if self.isFileIn():
            self.interrupt = False
            self.error_has_shown = False
            self.dict_args = {}
            self.dict_args["workPath"] = self.workPath
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + \
                                                      os.sep + "HmmCleaner_results" + os.sep + self.output_dir_name)
            self.dict_args["exportPath"] = self.exportPath
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                # 提醒是否删除旧结果,如果用户取消,就不执行
                return
            costs = "\"%.2f\" \"%.2f\" \"%.2f\" \"%.2f\"" % (
                self.doubleSpinBox.value(), self.doubleSpinBox_2.value(),
                self.doubleSpinBox_3.value(), self.doubleSpinBox_4.value())
            self.dict_args[
                "costs"] = " -costs %s" % costs if costs != '"-0.15" "-0.08" "0.15" "0.45"' else ""
            self.dict_args["noX"] = " --noX" if self.checkBox_5.isChecked(
            ) else ""
            self.dict_args[
                "specificity"] = " --specificity" if self.checkBox.isChecked(
                ) else ""
            self.dict_args["large"] = " --large" if self.checkBox_2.isChecked(
            ) else ""
            self.dict_args[
                "log_only"] = " --log_only" if self.checkBox_3.isChecked(
                ) else ""
            self.dict_args["ali"] = " --ali" if self.checkBox_4.isChecked(
            ) else ""
            self.dict_args[
                "changeID"] = " --changeID" if self.checkBox_6.isChecked(
                ) else ""
            self.dict_args[
                "profile"] = " -profile=%s" % self.comboBox_3.currentText()
            self.dict_args[
                "symfrac"] = " -symfrac %s" % self.doubleSpinBox_6.value(
                ) if self.doubleSpinBox_6.value() != 0.50 else ""
            self.dict_args[
                "verbosity"] = " -v=%s" % self.comboBox_5.currentText(
                ) if self.comboBox_5.currentText() != "0" else ""
            self.dict_args[
                "perl"] = "\"%s\" " % self.perl if self.HmmCleanerPath != "HmmCleaner.pl" else ""  #如果是用的环境变量的脚本,就不用perl
            self.dict_args["HmmCleaner"] = self.HmmCleanerPath
            ##输入文件
            self.dict_args["inputFiles"] = []
            try:
                for aln_file in self.comboBox_4.fetchListsText():
                    with open(aln_file, encoding="utf-8",
                              errors="ignore") as f:
                        content = f.read()
                    copy_path = self.exportPath + os.sep + os.path.basename(
                        aln_file)
                    with open(copy_path, "w", encoding="utf-8") as f1:
                        f1.write(content.replace("\r\n", "\n"))
                    self.dict_args["inputFiles"].append(copy_path)
            except:
                QMessageBox.information(
                    self, "HmmCleaner",
                    "<p style='line-height:25px; height:25px'>File copying failed, please check your input files!</p>"
                )
            command = "{perl}\"{HmmCleaner}\" $alignment${costs}{noX}{specificity}{large}{log_only}{ali}" \
                      "{changeID}{profile}{symfrac}{verbosity}".format(**self.dict_args)
            self.reference = "Di Franco A, Poujol R, Baurain D, Philippe H. 2019. Evaluating the usefulness of " \
                             "alignment filtering methods to reduce the impact of errors on evolutionary " \
                             "inferences. BMC Evol Biol. 19: 21. doi: 10.1186/s12862-019-1350-2."
            cmd_used = "{costs}{specificity}{large}{log_only}{ali}{changeID}{profile}{symfrac}".format(
                **self.dict_args).strip()
            self.description = "Low similarity segments within the alignment were removed with HmmCleaner (Di Franco et al., 2019) using \"%s\" command." % cmd_used
            self.textEdit_log.clear()  # 清空
            return command
        else:
            QMessageBox.critical(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>Please input files first!</p>"
            )

    def updateProcess(self):
        if self.queue.empty(): return
        info = self.queue.get()
        if info[0] == "log":
            message = info[1]
            self.logGuiSig.emit(message)
        elif info[0] == "prog":
            self.finishedFileNum += 1
            if not self.interrupt:
                self.progressSig.emit(self.finishedFileNum * 100 /
                                      self.totalFileNum)
                self.workflow_progress.emit(self.finishedFileNum * 100 /
                                            self.totalFileNum)
        elif info[0] == "popen":
            self.list_pids.append(info[1])
        elif info[0] == "error":
            self.on_pushButton_2_clicked(quiet=True)  #杀掉进程
            self.HmmCleaner_exception.emit(
                "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!"
            )
            self.error_has_shown = True
        elif info[0] == "popen finished":
            if info[1] in self.list_pids:
                self.list_pids.remove(info[1])

    def popup_HmmCleaner_exception(self, text):
        if not self.error_has_shown:
            QMessageBox.critical(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>%s</p>" % text)
            if "Show log" in text:
                self.on_pushButton_9_clicked()

    def popupAliWarning(self, bool_):
        if bool_:
            QMessageBox.warning(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>\"Ali\" format cannot be used by the downstream programs "
                "(e.g. IQ-TREE), please uncheck it if you are going to use this result for other functions.</p>"
            )

    def popupAutoDec(self):
        popupUI = self.factory.popUpAutoDetect("HmmCleaner", self.workPath,
                                               self)
        if not popupUI:
            QMessageBox.warning(
                self, "Warning",
                "<p style='line-height:25px; height:25px'>No available file detected!</p>"
            )
            return
        popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(autoInputs)

    def fetchWorkflowSetting(self):
        '''* Alignment Mode
          * Code table(if codon mode)
          * strategy
          * export format'''
        settings = '''<p class="title">***HmmCleaner***</p>'''
        c1 = self.doubleSpinBox.value()
        c2 = self.doubleSpinBox_2.value()
        c3 = self.doubleSpinBox_3.value()
        c4 = self.doubleSpinBox_4.value()
        settings += '<p>costs: \"<a href="self.HmmCleaner_exe doubleSpinBox.setFocus() doubleSpinBox.selectAll() ' \
                    'factory.highlightWidgets(x.doubleSpinBox)">%s</a>\" ' \
                    '\"<a href="self.HmmCleaner_exe doubleSpinBox_2.setFocus() doubleSpinBox_2.selectAll() ' \
                    'factory.highlightWidgets(x.doubleSpinBox_2)">%s</a>\" ' \
                    '\"<a href="self.HmmCleaner_exe doubleSpinBox_3.setFocus() doubleSpinBox_3.selectAll() ' \
                    'factory.highlightWidgets(x.doubleSpinBox_3)">%s</a>\" ' \
                    '\"<a href="self.HmmCleaner_exe doubleSpinBox_4.setFocus() doubleSpinBox_4.selectAll() ' \
                    'factory.highlightWidgets(x.doubleSpinBox_4)">%s</a>\"</p>' % (c1, c2, c3, c4)
        verbosity = self.comboBox_5.currentText()
        settings += '<p>verbosity: <a href="self.HmmCleaner_exe comboBox_5.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % verbosity
        profile = self.comboBox_3.currentText()
        settings += '<p>profile: <a href="self.HmmCleaner_exe comboBox_3.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % profile
        thread = self.comboBox_6.currentText()
        settings += '<p>Thread: <a href="self.HmmCleaner_exe comboBox_6.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread
        specificity = "Yes" if self.checkBox.isChecked() else "No"
        settings += '<p>specificity: <a href="self.HmmCleaner_exe' \
                    ' factory.highlightWidgets(x.checkBox)">%s</a></p>' % specificity
        large = "Yes" if self.checkBox_2.isChecked() else "No"
        settings += '<p>large: <a href="self.HmmCleaner_exe' \
                    ' factory.highlightWidgets(x.checkBox_2)">%s</a></p>' % large
        changeID = "Yes" if self.checkBox_6.isChecked() else "No"
        settings += '<p>changeID: <a href="self.HmmCleaner_exe' \
                    ' factory.highlightWidgets(x.checkBox_6)">%s</a></p>' % changeID
        noX = "Yes" if self.checkBox_5.isChecked() else "No"
        settings += '<p>noX: <a href="self.HmmCleaner_exe' \
                    ' factory.highlightWidgets(x.checkBox_5)">%s</a></p>' % noX
        return settings

    def isFileIn(self):
        return self.comboBox_4.count()
Пример #9
0
class Gblocks(QDialog, Ui_Gblocks, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    logGuiSig = pyqtSignal(str)
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    auto_parSig = pyqtSignal()
    gblocks_exception = pyqtSignal(str)  # 定义所有类都可以使用的信号
    # 用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)
    # 用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)
    ##弹出识别输入文件的信号
    auto_popSig = pyqtSignal(QDialog)

    def __init__(self,
                 autoInputs=None,
                 workPath=None,
                 focusSig=None,
                 gb_exe=None,
                 workflow=False,
                 parent=None):
        super(Gblocks, self).__init__(parent)
        self.parent = parent
        self.function_name = "Gblocks"
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig if focusSig else pyqtSignal(
            str)  # 为了方便workflow
        self.workflow = workflow
        self.gb_exe = '"' + gb_exe + '"'
        self.autoInputs = autoInputs
        self.interrupt = False
        self.setupUi(self)
        # 保存设置
        if not workflow:
            self.gblocks_settings = QSettings(
                self.thisPath + '/settings/gblocks_settings.ini',
                QSettings.IniFormat)
        else:
            self.gblocks_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini',
                QSettings.IniFormat)
            self.gblocks_settings.beginGroup("Workflow")
            self.gblocks_settings.beginGroup("temporary")
            self.gblocks_settings.beginGroup('Gblocks')
        # File only, no fallback to registry or or.
        self.gblocks_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.log_gui = self.gui4Log()
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.gblocks_exception.connect(self.popup_Gblocks_exception)
        self.progressSig.connect(self.runProgress)
        self.logGuiSig.connect(self.addText2Log)
        self.comboBox_4.installEventFilter(self)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  # 自动识别可用的输入
        # self.comboBox_4.itemRemovedSig.connect(self.auto_parFromfile)
        self.comboBox_2.currentIndexChanged[str].connect(self.comboLink)
        reg_ex = QRegExp(".{,5}")
        validator = QRegExpValidator(reg_ex, self.lineEdit_3)
        self.lineEdit_3.setValidator(validator)
        self.auto_parSig.connect(self.auto_parFromfile)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_7.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-6-1-Brief-example"
            )))
        ##自动弹出识别文件窗口
        self.auto_popSig.connect(self.popupAutoDecSub)

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        if self.isFileIn():
            # 创建输出文件夹
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + os.sep +
                                                      "Gblocks_results" +
                                                      os.sep +
                                                      self.output_dir_name)
            self.interrupt = False
            ok = self.factory.remove_dir(self.exportPath,
                                         parent=self)  # 第二次运行的时候要清空
            if not ok:
                # 提醒是否删除旧结果,如果用户取消,就不执行
                return
            self.collect_args()
            self.textEdit_log.clear()  # 清空log
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.critical(
                self, "Gblocks",
                "<p style='line-height:25px; height:25px'>Please input files first!</p>"
            )

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        open files
        """
        fileNames = QFileDialog.getOpenFileNames(
            self,
            "Input Files",
            filter="NBRF/PIR Format(*.pir);;Fasta Format(*.fas *.fasta *.fsa);;"
        )
        if fileNames[0]:
            self.input(fileNames[0])

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        show log
        """
        self.log_gui.show()

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        """
        Stop
        """
        if self.isRunning():
            if not self.workflow:
                reply = QMessageBox.question(
                    self, "Confirmation",
                    "<p style='line-height:25px; height:25px'>Gblocks is still running, terminate it?</p>",
                    QMessageBox.Yes, QMessageBox.Cancel)
            else:
                reply = QMessageBox.Yes
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    if platform.system().lower() in ["windows", "darwin"]:
                        self.gb_popen.kill()
                    else:
                        os.killpg(os.getpgid(self.gb_popen.pid),
                                  signal.SIGTERM)
                    self.gb_popen = None
                    self.interrupt = True
                except:
                    self.gb_popen = None
                    self.interrupt = True
                if not self.workflow:
                    QMessageBox.information(
                        self, "Gblocks",
                        "<p style='line-height:25px; height:25px'>Program has been terminated!</p>"
                    )
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])

    def run_command(self):
        try:
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start", self.exportPath,
                self.qss_file, self
            ])
            # if not self.workflow:
            self.input_files = []
            for j in self.files:
                shutil.copy(j, self.exportPath)
                self.input_files.append(os.path.basename(j))
            os.chdir(self.exportPath)
            # self.input_files = os.listdir(self.exportPath)
            for num, self.input_file in enumerate(self.input_files):
                if self.seq_num_differ and (self.workflow or self.runAnyway):
                    with open(self.input_file,
                              encoding="utf-8",
                              errors='ignore') as f:
                        content = f.read()
                        self.seq_num = content.count(">")
                    self.auto_parFromfile()
                    # self.auto_parSig.emit()
                    self.collect_args()  # 参数刷新了
                commands = "{self.gb_exe} \"{self.input_file}\" -t={self.t} -b1={self.b1} -b2={self.b2} -b3={self.b3} -b4={self.b4} -b5={self.b5}{self.b6} -s={self.s} -p={self.p} -v={self.v} -n={self.n} -u={self.u} -k={self.k} -d={self.d} -e={self.e}".format(
                    self=self)
                # print(os.path.basename(self.input_file), commands)
                self.run_code(commands)
                self.progressSig.emit((num + 1) * 100 / len(self.input_files))
                self.workflow_progress.emit(
                    (num + 1) * 100 / len(self.input_files))
                if self.interrupt:
                    return
            # 将gb后缀放到前面
            for j in os.listdir(self.exportPath):
                name, ext = os.path.splitext(j)
                if ext.endswith(self.e):
                    # 将ext的后缀放到前面
                    if not os.path.exists(self.exportPath + os.sep + name +
                                          self.e + ".fasta"):
                        os.rename(
                            self.exportPath + os.sep + j, self.exportPath +
                            os.sep + name + self.e + ".fasta")
                    else:
                        try:
                            os.remove(self.exportPath + os.sep + j)
                        except:
                            pass
                if self.interrupt:
                    break
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), self.time_used)
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    self.description +
                    "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n"
                    "If you use Gblocks, please cite:\n" + self.reference +
                    "\n\n" + self.time_used_des)
            if not self.interrupt:
                if self.workflow:
                    # work flow跑的
                    self.startButtonStatusSig.emit([
                        self.pushButton, self.progressBar, "workflow stop",
                        self.exportPath, self.qss_file, self
                    ])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop", self.exportPath,
                    self.qss_file, self
                ])
                self.focusSig.emit(self.exportPath)
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except", self.exportPath,
                self.qss_file, self
            ])

    def guiSave(self):
        # Save geometry
        self.gblocks_settings.setValue('size', self.size())
        # self.gblocks_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                if name != "comboBox_4":
                    text = obj.currentText()
                    if text:
                        allItems = [
                            obj.itemText(i) for i in range(obj.count())
                        ]
                        allItems.remove(text)
                        sortItems = [text] + allItems
                        self.gblocks_settings.setValue(name, sortItems)
            if isinstance(obj, QRadioButton):
                state = obj.isChecked()
                self.gblocks_settings.setValue(name, state)
            if isinstance(obj, QLineEdit):
                text = obj.text()
                self.gblocks_settings.setValue(name, text)
            if isinstance(obj, QSpinBox):
                value = obj.value()
                self.gblocks_settings.setValue(name, value)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.gblocks_settings.value('size', QSize(658, 500)))
        self.factory.centerWindow(self)
        # self.move(self.gblocks_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name not in ["comboBox_10", "comboBox_2"]:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    values = self.gblocks_settings.value(name, allItems)
                    if name == "comboBox_4":
                        if not self.workflow:
                            if self.autoInputs:
                                self.input(self.autoInputs)
                            else:
                                self.input(values)
                        else:
                            self.input([])
                    else:
                        model = obj.model()
                        obj.clear()
                        for num, i in enumerate(values):
                            item = QStandardItem(i)
                            # 背景颜色
                            if num % 2 == 0:
                                item.setBackground(QColor(255, 255, 255))
                            else:
                                item.setBackground(QColor(237, 243, 254))
                            model.appendRow(item)
            if isinstance(obj, QRadioButton):
                value = self.gblocks_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QLineEdit):
                value = self.gblocks_settings.value(
                    name, "_gb")  # get stored value from registry
                obj.setText(value)
            if isinstance(obj, QSpinBox):
                value = self.gblocks_settings.value(
                    name)  # get stored value from registry
                if value:
                    obj.setValue(int(value))
        if self.radioButton_2.isChecked():
            self.comboBox_13.setEnabled(True)
        elif self.radioButton.isChecked() or self.radioButton_3.isChecked():
            self.comboBox_13.setEnabled(False)

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.guiSave()
        self.log_gui.close()  # 关闭子窗口
        self.closeSig.emit("Gblocks", self.fetchWorkflowSetting())
        # 取消选中文字
        self.radioButton.setFocus()
        self.spinBox_2.lineEdit().deselect()
        self.spinBox_3.lineEdit().deselect()
        # 断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("Gblocks")
            # 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            # print(self.isRunning())
            reply = QMessageBox.question(
                self, "Gblocks",
                "<p style='line-height:25px; height:25px'>Gblocks is still running, terminate it?</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    if platform.system().lower() in ["windows", "darwin"]:
                        self.gb_popen.kill()
                    else:
                        os.killpg(os.getpgid(self.gb_popen.pid),
                                  signal.SIGTERM)
                    self.gb_popen = None
                    self.interrupt = True
                except:
                    self.gb_popen = None
                    self.interrupt = True
            else:
                event.ignore()

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                pick_files = [
                    i for i in files if os.path.splitext(i)[1].upper() in
                    [".FASTA", ".FAS", ".PIR", ".NBRF"]
                ]
                if pick_files:
                    self.input(files)
                else:
                    QMessageBox.warning(
                        self, "Gblocks",
                        "<p style='line-height:25px; height:25px'>Only files in fasta, nbrf and pir formats are allowed!</p>"
                    )
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        return super(Gblocks, self).eventFilter(obj, event)  # 0

    def input(self, infiles):
        self.comboBox_4.refreshInputs([])  # 先刷新一个空的
        dict_taxon_num = {}
        self.seq_num_differ = False  # 序列数目相同就是false
        self.runAnyway = False
        self.comboBox_10.setDisabled(False)
        self.comboBox_2.setDisabled(False)
        new_infiles = []  # 预防有时候有些文件无效
        for i in infiles:
            if not os.path.exists(i):
                continue
            with open(i, encoding="utf-8", errors='ignore') as f:
                content = f.read()
                self.seq_num = content.count(">")
                if self.seq_num == 0:
                    self.popup_Gblocks_exception(
                        "The file %s is not in fasta format!" %
                        (os.path.basename(i)))
                    self.comboBox_4.refreshInputs([])
                    return
                try:
                    if re.search(r"(?m)^\n(?!\>)", content):
                        # 序列内部有空行
                        content = re.sub(r"(?m)^\n(?!\>)", "", content)
                        with open(i, "w", encoding="utf-8") as f1:
                            # 重新保存一下该文件
                            f1.write(content)
                    else:
                        # 多了一个\r的情况
                        with open(i, "w", encoding="utf-8") as f1:
                            # 重新保存一下该文件
                            f1.write(content)
                except:
                    pass
                dict_taxon_num.setdefault(self.seq_num,
                                          []).append(os.path.basename(i))
            new_infiles.append(i)
        if not new_infiles:  # 如果没有有效的文件
            return
        if len(dict_taxon_num) > 1:
            self.seq_num_differ = True
        # 物种数目一样,如果是workflow方式,就直接都用最低配置
        if len(dict_taxon_num) == 1 or self.workflow:
            self.auto_parFromfile()
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Information)
            msg.setText(
                'Input files contain different numbers of sequences (which implies that some genes are missing), '
                'execute Gblocks with most relaxed parameters to retain as much data as possible?'
            )
            msg.setWindowTitle("Gblocks")
            list_detail = ["Seqs  |  Files"] + [
                str(i).ljust(6) + "|  " + str(dict_taxon_num[i])
                for i in dict_taxon_num
            ]
            msg.setDetailedText("\n".join(list_detail))
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            if msg.exec_() == QMessageBox.Yes:
                self.comboBox_10.setDisabled(True)
                self.comboBox_2.setDisabled(True)
                self.runAnyway = True
                self.auto_parFromfile()
            else:
                return
        # 输入序列
        self.comboBox_4.refreshInputs(infiles)

    def comboLink(self, text):
        if text:  # 有时候清空combobox会报错
            list_first_opt = []
            for i in range(int(text) + 1):
                if i >= self.seq_num // 2 + 1:
                    list_first_opt.append(str(i))
            self.combobox_refresh(self.comboBox_10, list_first_opt)
            # self.comboBox_10.setCurrentIndex(0)

    def run_code(self, commands):
        startupINFO = None
        if platform.system().lower() == "windows":
            startupINFO = subprocess.STARTUPINFO()
            startupINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupINFO.wShowWindow = subprocess.SW_HIDE
            self.gb_popen = subprocess.Popen(commands,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.STDOUT,
                                             startupinfo=startupINFO)
        else:
            self.gb_popen = subprocess.Popen(commands,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.STDOUT,
                                             startupinfo=startupINFO,
                                             shell=True,
                                             preexec_fn=os.setsid)
        self.factory.emitCommands(self.logGuiSig, commands)
        is_error = False  ##判断是否出了error
        while True:
            QApplication.processEvents()
            if self.isRunning():
                try:
                    out_line = self.gb_popen.stdout.readline().decode(
                        "utf-8", errors='ignore')
                except UnicodeDecodeError:
                    out_line = self.gb_popen.stdout.readline().decode(
                        "gbk", errors='ignore')
                if out_line == "" and self.gb_popen.poll() is not None:
                    break
                self.logGuiSig.emit(out_line.strip())
                if "Execution terminated" in out_line:
                    is_error = True
            else:
                break
        if is_error:
            self.interrupt = True
            self.gblocks_exception.emit(
                "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!"
            )
        self.gb_popen = None

    def addText2Log(self, text):
        if re.search(r"\w+", text):
            self.textEdit_log.append(text)
            with open(self.exportPath + os.sep + "PhyloSuite_Gblocks.log",
                      "a") as f:
                f.write(text + "\n")

    def gui4Log(self):
        dialog = QDialog(self)
        dialog.resize(800, 500)
        dialog.setWindowTitle("Log")
        gridLayout = QGridLayout(dialog)
        horizontalLayout_2 = QHBoxLayout()
        label = QLabel(dialog)
        label.setText("Log of Gblocks:")
        horizontalLayout_2.addWidget(label)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        horizontalLayout_2.addItem(spacerItem)
        toolButton = QToolButton(dialog)
        icon2 = QIcon()
        icon2.addPixmap(
            QPixmap(
                ":/picture/resourses/interface-controls-text-wrap-512.png"))
        toolButton.setIcon(icon2)
        toolButton.setCheckable(True)
        toolButton.setToolTip("Use Wraps")
        toolButton.clicked.connect(self.setWordWrap)
        toolButton.setChecked(True)
        horizontalLayout_2.addWidget(toolButton)
        pushButton = QPushButton("Save to file", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
        pushButton.setIcon(icon)
        pushButton_2 = QPushButton("Close", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png"))
        pushButton_2.setIcon(icon)
        self.textEdit_log = QTextEdit(dialog)
        self.textEdit_log.setReadOnly(True)
        gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2)
        gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2)
        gridLayout.addWidget(pushButton, 2, 0, 1, 1)
        gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
        pushButton.clicked.connect(self.save_log_to_file)
        pushButton_2.clicked.connect(dialog.close)
        dialog.setWindowFlags(dialog.windowFlags()
                              | Qt.WindowMinMaxButtonsHint)
        return dialog

    def setWordWrap(self):
        button = self.sender()
        if button.isChecked():
            button.setChecked(True)
            self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            button.setChecked(False)
            self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap)

    def save_log_to_file(self):
        content = self.textEdit_log.toPlainText()
        fileName = QFileDialog.getSaveFileName(self, "Gblocks", "log",
                                               "text Format(*.txt)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(content)

    def collect_args(self):
        self.files = self.comboBox_4.fetchListsText()
        dict_abbre = {
            "None": "n",
            "With Half": "h",
            "All": "a",
            "Yes": "y",
            "No": "n",
            "Save": "y",
            "Don't Save": "n",
            "Save Text": "t",
            "Save Short": "s"
        }
        if self.radioButton.isChecked():
            self.t = "d"
        elif self.radioButton_2.isChecked():
            self.t = "p"
        elif self.radioButton_3.isChecked():
            self.t = "c"
        self.b1 = self.comboBox_10.currentText()
        self.b2 = self.comboBox_2.currentText()
        self.b3 = str(self.spinBox_2.value())
        self.b4 = str(self.spinBox_3.value())
        self.b5 = dict_abbre[self.comboBox_5.currentText()]
        self.b6 = " -b6=%s" % dict_abbre[
            self.comboBox_13.currentText()] if self.t == "p" else ""
        self.s = dict_abbre[self.comboBox_6.currentText()]
        self.p = dict_abbre[self.comboBox_12.currentText()]
        self.v = str(self.spinBox.value())
        self.n = dict_abbre[self.comboBox_7.currentText()]
        self.u = dict_abbre[self.comboBox_8.currentText()]
        self.k = dict_abbre[self.comboBox_11.currentText()]
        self.d = dict_abbre[self.comboBox_9.currentText()]
        self.e = self.lineEdit_3.text()
        prefix = "Ambiguously aligned fragments of %d alignments were removed in batches" % self.comboBox_4.count(
        ) if self.comboBox_4.count(
        ) > 1 else "Ambiguously aligned fragments of 1 alignment was removed"
        self.description = '''%s using Gblocks (Talavera and Castresana, 2007) with the following parameter settings: minimum number of sequences for a conserved/flank position (%s/%s), maximum number of contiguous non-conserved positions (%s), minimum length of a block (%s), allowed gap positions (%s).''' % (
            prefix, self.b1, self.b2, self.b3, self.b4,
            self.comboBox_5.currentText().lower())
        self.reference = "Talavera, G., Castresana, J., 2007. Improvement of phylogenies after removing divergent and ambiguously aligned blocks from protein sequence alignments. Syst. Biol. 56, 564-577."

    def auto_parFromfile(self):
        list_second_opt = []  # 设置第二个选项
        self.seq_range_num = self.seq_num + 1  # 必须加1,参数才会正确
        for j in range(self.seq_range_num):
            if j >= self.seq_num // 2 + 1:
                list_second_opt.append(str(j))
        self.combobox_refresh(self.comboBox_2, list_second_opt)
        # self.comboBox_2.setCurrentIndex(0)
        list_first_opt = []  # 设置第一个选项
        for k in range(int(self.comboBox_2.currentText()) + 1):
            if k >= self.seq_num // 2 + 1:
                list_first_opt.append(str(k))
        self.combobox_refresh(self.comboBox_10, list_first_opt)

    def check_MSA(self, files):
        self.unaligns = []
        self.dict_genes_alignments = OrderedDict()
        parsefmt = Parsefmt("")
        for num, eachFile in enumerate(files):
            geneName = os.path.splitext(os.path.basename(eachFile))[0]
            dict_taxon = parsefmt.readfile(eachFile)
            if self.factory.is_aligned(dict_taxon):
                self.dict_genes_alignments[geneName] = dict_taxon
            else:
                self.unaligned = True
                self.unaligns.append(geneName)
        # 补足数据
        dict_warning_data = OrderedDict()
        dict_maxTaxa = []
        for k in self.dict_genes_alignments:
            list_taxon = list(self.dict_genes_alignments[k].keys())
            dict_maxTaxa.extend(list_taxon)
        list_set_dict_maxTaxa = list(set(dict_maxTaxa))
        self.seq_num = len(list_set_dict_maxTaxa)
        self.auto_parFromfile()
        for i in list_set_dict_maxTaxa:
            lossingGene = []
            for j in self.dict_genes_alignments:
                if i not in self.dict_genes_alignments[j]:
                    keys = list(self.dict_genes_alignments[j].keys())
                    alignmentLenth = len(
                        self.dict_genes_alignments[j][keys[0]])
                    self.dict_genes_alignments[j][i] = "?" * alignmentLenth
                    lossingGene.append(j)
            if lossingGene:
                dict_warning_data[i] = lossingGene
        # 覆盖以前的文件
        self.input_files = []
        convertfmt = Convertfmt(**{
            "export_path": self.exportPath,
            "export_fas": True
        })
        for name in self.dict_genes_alignments:
            convertfmt.generate_each(self.dict_genes_alignments[name], name)
            self.input_files.append(convertfmt.f6)
        # 生成报错信息
        if dict_warning_data:
            max_len_taxa = len(max(list(dict_warning_data), key=len))
            # 要大于species的占位符
            max_len_taxa = max_len_taxa if max_len_taxa > 7 else 7
            list_detail = [
                "Species".ljust(max_len_taxa) + " |Missing genes"
            ] + [
                str(i).ljust(max_len_taxa) + " |" + str(dict_warning_data[i])
                for i in dict_warning_data
            ]
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Warning)
            msg.setText(
                "<p style='line-height:25px; height:25px'>Missing genes are replaced with '?' (see details or 'missing_genes.txt')</p>"
            )
            msg.setWindowTitle("Warning")
            msg.setDetailedText("\n".join(list_detail))
            msg.setStandardButtons(QMessageBox.Ok)
            with open(self.exportPath + os.sep + "missing_genes.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write("\n".join(list_detail))
            msg.exec_()
            # if msg.exec_() == 1024:  # QDialog.Accepted:
            #     if self.workflow:
            #         ##work flow跑的
            #         self.startButtonStatusSig.emit(
            #             [
            #                 self.pushButton,
            #                 self.progressBar,
            #                 "workflow stop",
            #                 self.exportPath,
            #                 self.qss_file,
            #                 self])
            #         self.workflow_finished.emit("finished")
            #         return
            #     self.startButtonStatusSig.emit(
            #         [self.pushButton, self.progressBar, "stop", self.exportPath, self.qss_file, self])
            #     self.focusSig.emit(self.exportPath)

    def combobox_refresh(self, widget, list_items):
        model = widget.model()
        widget.clear()
        for num, i in enumerate(list_items):
            item = QStandardItem(i)
            # 背景颜色
            if num % 2 == 0:
                item.setBackground(QColor(255, 255, 255))
            else:
                item.setBackground(QColor(237, 243, 254))
            model.appendRow(item)
        widget.setCurrentIndex(0)

    def isRunning(self):
        '''判断程序是否运行,依赖进程是否存在来判断'''
        return hasattr(self,
                       "gb_popen") and self.gb_popen and not self.interrupt

    def popup_Gblocks_exception(self, text):
        QMessageBox.information(
            self, "Gblocks",
            "<p style='line-height:25px; height:25px'>%s</p>" % text)
        if "Show log" in text:
            self.on_pushButton_9_clicked()

    def popupAutoDec(self, init=False):
        self.init = init
        self.factory.popUpAutoDetect("Gblocks", self.workPath,
                                     self.auto_popSig, self)

    def popupAutoDecSub(self, popupUI):
        if not popupUI:
            if not self.init:
                QMessageBox.warning(
                    self, "Warning",
                    "<p style='line-height:25px; height:25px'>No available file detected!</p>"
                )
            return
        if not self.init: popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(autoInputs)

    def fetchWorkflowSetting(self):
        '''* Data type
          * Minimum Number Of Sequences For A Conserved Position:
          * Minimum Number Of Sequences For A Flank Position:
          * Maximum Number Of Contiguous Nonconserved Positions:
          * Minimum Length Of A Block:
          * Allowed Gap Positions:'''
        settings = '''<p class="title">***Gblocks***</p>'''
        if self.radioButton.isChecked():
            data_type = "Nucleotide"
        elif self.radioButton_2.isChecked():
            data_type = "Protein"
        elif self.radioButton_3.isChecked():
            data_type = "Codons"
        settings += '<p>Data type: <a href="self.Gblocks_exe factory.highlightWidgets(x.radioButton,' \
                    'x.radioButton_2,x.radioButton_3)">%s</a></p>' % data_type
        conserve_pos = self.comboBox_10.currentText()
        if conserve_pos:
            settings += '<p>Minimum Number Of Sequences For A Conserved Position: <a href="self.Gblocks_exe ' \
                        'comboBox_10.showPopup() factory.highlightWidgets(x.comboBox_10)">%s</a></p>' % conserve_pos
        else:
            settings += '<p>Minimum Number Of Sequences For A Conserved Position: <span style="font-weight:600; color:green;">auto detect when loading files</span></p>'
        flank_pos = self.comboBox_2.currentText()
        if flank_pos:
            settings += '<p>Minimum Number Of Sequences For A Conserved Position: <a href="self.Gblocks_exe ' \
                        'comboBox_2.showPopup() factory.highlightWidgets(x.comboBox_2)">%s</a></p>' % flank_pos
        else:
            settings += '<p>Minimum Number Of Sequences For A Flank Position: <span style="font-weight:600; color:green;">auto detect when loading files</span></p>'
        max_pos = self.spinBox_2.value()
        settings += '<p>Maximum Number Of Contiguous Nonconserved Positions: ' \
                    '<a href="self.Gblocks_exe spinBox_2.setFocus() spinBox_2.selectAll()' \
                    ' factory.highlightWidgets(x.spinBox_2)">%s</a></p>' % max_pos
        min_block = self.spinBox_3.value()
        settings += '<p>Minimum Length Of A Block: <a href="self.Gblocks_exe spinBox_3.setFocus() spinBox_3.selectAll()' \
                    ' factory.highlightWidgets(x.spinBox_3)">%s</a></p>' % min_block
        allow_gap = self.comboBox_5.currentText()
        settings += '<p>Allowed Gap Positions: <a href="self.Gblocks_exe ' \
                    'comboBox_5.showPopup() factory.highlightWidgets(x.comboBox_5)">%s</a></p>' % allow_gap
        return settings

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))
        # self.showSig.emit(self)

    def isFileIn(self):
        return self.comboBox_4.count()
Пример #10
0
class UpdateAPP(QDialog):
    '''
    1.先下载新的app
    2.然后解压,由于github下载的,解压会自动解压到一个新的文件夹
    3.从解压的文件夹把文件移动到跟文件夹,此时判断一下文件是否存在,如果存在,就重命名.old(主程序每次启动,都会判断是否有.old文件存在,
      如果有就删除;这一步如果无法重命名的话,有可能会造成失败),然后再把文件移动过去(settings和plugins文件夹会跳过)
    4.重新启动程序,然后删除.old文件
    '''
    progressSig = pyqtSignal(int)  # 控制进度条

    def __init__(self, parent=None):
        super(UpdateAPP, self).__init__(parent)
        self.factory = Factory()
        self.rootPath = self.factory.thisPath
        #os.path.dirname(os.path.realpath(__file__))
        # self.totalSize = totalSize if totalSize else "42389760"
        # self.verticalLayout = QVBoxLayout(self)
        # self.setWindowTitle("Update")
        # self.setAttribute(Qt.WA_TranslucentBackground)
        # self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
        # # self.setModal(Qt.WindowModal)
        # self.progressBar = PercentProgressBar(self, showSmallCircle=True)
        # self.verticalLayout.addWidget(self.progressBar)
        # self.label = QLabel("<span style='font-weight:600; color:white; font: 35px;'>Downloading...</span>", self)
        # self.verticalLayout.addWidget(self.label)
        # self.progressSig.connect(self.downloadProgress)

    def exec_unzip(self):
        # self.label.setText("<span style='font-weight:600; color:white; font: 35px;'>Unpacking new file</span>")
        self.worker = WorkThread(self.unzipNewApp, parent=self)
        self.worker.start()
        self.worker.finished.connect(self.exec_restart)

    def exec_restart(self):
        # self.label.setText("Update successfully")
        python = sys.executable
        os.execl(python, python, *sys.argv)

    def downloadNewAPP(self):
        if platform.system().lower() == "windows":
            url = "https://raw.githubusercontent.com/dongzhang0725/PhyloSuite/master/update.zip"
        elif platform.system().lower() == "darwin":
            url = "https://raw.githubusercontent.com/dongzhang0725/PhyloSuite_Mac/master/update.zip"
        elif platform.system().lower() == "linux":
            url = "https://raw.githubusercontent.com/dongzhang0725/PhyloSuite_linux/master/update.zip"
        self.downloadPath = self.rootPath + os.sep + "update.zip"
        ###qhttp下载
        dict_args = {}
        dict_args["url"] = url
        dict_args["downloadPath"] = self.downloadPath
        # dict_args["totalSize"] = self.totalSize
        dict_args["progressSig"] = self.progressSig
        httpwindow = HttpWindowDownload(parent=self, **dict_args)
        httpwindow.finishSig.connect(self.exec_unzip)
        httpwindow.ini_args()

    def unzipNewApp(self):
        file_zip = zipfile.ZipFile(self.downloadPath, 'r')
        namelists = file_zip.namelist()
        for num, file in enumerate(namelists):
            path_root = self.rootPath + os.sep + os.path.basename(file)
            if os.path.exists(path_root):
                ##如果已经存在的文件夹就替换名字
                if os.path.basename(file) not in ["settings", "plugins"]:
                    ##SRC和插件保留
                    try:
                        ##如果重命名失败,也不能移动文件,这里是可能造成更新失败的地方
                        os.rename(path_root, path_root + ".old")
                    except:
                        pass
            try:
                file_zip.extract(file, self.rootPath)
                if platform.system().lower() in ["darwin", "linux"]:
                    os.system("chmod 755 %s" % (self.rootPath + os.sep + file))
            except:
                pass
            self.progressSig.emit(95 + 5 * ((num + 1) / len(namelists)))
        file_zip.close()
        try:
            os.remove(self.downloadPath)
        except:
            pass
Пример #11
0
class Lg_ReANNT(QDialog, Ui_tRNA_ANNT, object):
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    close_sig = pyqtSignal()
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号

    def __init__(self, unRecognisetRNA=None, gbContent=None, predict_signal=None, parent=None):
        super(Lg_ReANNT, self).__init__(parent)
        # self.thisPath = os.path.dirname(os.path.realpath(__file__))
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.setupUi(self)
        self.textBrowser.setText(unRecognisetRNA)
        self.gbContent = gbContent
        self.predict_signal = predict_signal
        self.totaltRNA = unRecognisetRNA.count(">")
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss', encoding="utf-8", errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 信号和槽
        self.progressSig.connect(self.normProgress)
        self.close_sig.connect(self.closeWindow)
        self.exception_signal.connect(self.popupException)

    @pyqtSlot()
    def on_pushButton_clicked(self):
        text = self.textBrowser.toPlainText()
        fileName = QFileDialog.getSaveFileName(
            self, "Predict tRNA", "tRNAs", "fasta Format(*.fas)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(text)
            QMessageBox.information(
                self,
                "Predict tRNA",
                "<p style='line-height:25px; height:25px'>File saved successfully!</p>")

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        self.arwenContents = self.textBrowser_2.toPlainText()
        if self.arwenContents:
            # 进度条
            self.progressDialog = self.factory.myProgressDialog(
                "Please Wait", "replacing...", parent=self)
            self.progressDialog.show()
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.information(
                self,
                "Predict tRNA",
                "<p style='line-height:25px; height:25px'>No ARWEN results found!</p>")

    def run_command(self):
        try:
            # raise BaseException
            self.startButtonStatusSig.emit(
                [
                    self.pushButton_2,
                    None,
                    "start",
                    None,
                    self.qss_file,
                    self])
            self.reANNT = ReANNT(
                self.arwenContents, self.gbContent, self.totaltRNA, self.progressSig)
            self.startButtonStatusSig.emit(
                [self.pushButton_2, None, "simple stop", None, self.qss_file, self])
            self.close_sig.emit()
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit(
                [
                    self.pushButton,
                    self.progressBar,
                    "except",
                    self.dict_args["exportPath"],
                    self.qss_file,
                    self])

    def normProgress(self, num):
        oldValue = self.progressDialog.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressDialog.setProperty("value", done_int)
            QCoreApplication.processEvents()
            if done_int == 100:
                self.progressDialog.close()

    def closeWindow(self):
        self.progressDialog.close()
        self.close()
        self.predict_signal.emit(self.reANNT.gbContent)

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]')
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()
Пример #12
0
class TrimAl(QDialog, Ui_trimAl, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    logGuiSig = pyqtSignal(str)
    trimAl_exception = pyqtSignal(str)
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    # 用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)
    # 用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)
    ##弹出识别输入文件的信号
    auto_popSig = pyqtSignal(QDialog)

    def __init__(self,
                 workPath=None,
                 TApath=None,
                 autoInputs=None,
                 focusSig=None,
                 workflow=None,
                 parent=None):
        super(TrimAl, self).__init__(parent)
        self.parent = parent
        self.function_name = "trimAl"
        self.workflow = workflow
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig
        self.TApath = TApath
        self.autoInputs = autoInputs
        self.setupUi(self)
        # 保存设置
        if not workflow:
            self.trimAl_settings = QSettings(
                self.thisPath + '/settings/trimAl_settings.ini',
                QSettings.IniFormat)
        else:
            self.trimAl_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini',
                QSettings.IniFormat)
            self.trimAl_settings.beginGroup("Workflow")
            self.trimAl_settings.beginGroup("temporary")
            self.trimAl_settings.beginGroup('trimAl')
        # File only, no fallback to registry or or.
        self.trimAl_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.logGuiSig.connect(self.addText2Log)
        self.trimAl_exception.connect(self.popup_trimAl_exception)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  # 自动识别可用的输入
        self.comboBox_4.installEventFilter(self)
        self.lineEdit_2.installEventFilter(self)
        self.lineEdit_3.installEventFilter(self)
        self.log_gui = self.gui4Log()
        # stat output file name
        self.lineEdit_2.setLineEditNoChange(True)
        self.lineEdit_3.setLineEditNoChange(True)
        self.lineEdit_2.deleteFile.clicked.connect(
            self.clear_lineEdit)  # 删除了内容,也要把tooltip删掉
        self.lineEdit_3.deleteFile.clicked.connect(
            self.clear_lineEdit)  # 删除了内容,也要把tooltip删掉
        # 选框联动
        self.checkBox_2.toggled.connect(self.switchCheckBox1)
        self.checkBox_5.toggled.connect(self.switchCheckBox1)
        # self.groupBox_5.toggled.connect(lambda bool_: self.groupBox_6.setChecked(not bool_))
        # self.groupBox_6.toggled.connect(lambda bool_: self.groupBox_5.setChecked(not bool_))
        self.checkBox_3.toggled.connect(self.switchCheckBox2)
        self.checkBox_6.toggled.connect(self.switchCheckBox2)
        self.checkBox_7.toggled.connect(self.switchCheckBox2)
        self.checkBox_8.toggled.connect(self.switchCheckBox2)
        self.checkBox_9.toggled.connect(self.judgeInput)
        self.radioButton.toggled.connect(self.Un_checkBox_9)
        self.comboBox_2.currentTextChanged.connect(self.judgeComboBox_2)
        self.comboBox.currentTextChanged.connect(self.judgeFormats)
        self.radioButton.toggled.connect(self.controlRefineText)
        self.interrupt = False
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        action = QAction(QIcon(":/picture/resourses/terminal-512.png"),
                         "View | Edit command",
                         menu,
                         triggered=self.showCMD)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(action)
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_7.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-4-1-Brief-example"
            )))
        ##自动弹出识别文件窗口
        self.auto_popSig.connect(self.popupAutoDecSub)

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        self.command = self.fetchCommands()
        if self.command:
            self.interrupt = False
            self.error_has_shown = False  #保证只报一次错
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            thread = thread if len(
                self.dict_args["inputFiles"]) > thread else len(
                    self.dict_args["inputFiles"])
            thread = 1 if not self.dict_args[
                "inputFiles"] else thread  # compare的情况
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        show log
        """
        self.log_gui.show()

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        alignment file
        """
        fileNames = QFileDialog.getOpenFileNames(self, "Input alignment file")
        if fileNames[0]:
            self.input(fileNames[0])

    @pyqtSlot()
    def on_pushButton_4_clicked(self):
        """
        set file for comparison
        """
        fileName = QFileDialog.getOpenFileName(
            self, "Input file containing alignment path to compare")
        if fileName[0]:
            base = os.path.basename(fileName[0])
            self.lineEdit_2.setText(base)
            self.lineEdit_2.setToolTip(fileName[0])

    @pyqtSlot()
    def on_pushButton_22_clicked(self):
        """
        matrix file
        """
        fileName = QFileDialog.getOpenFileName(self, "Input matrix file")
        if fileName[0]:
            base = os.path.basename(fileName[0])
            self.lineEdit_3.setText(base)
            self.lineEdit_3.setToolTip(fileName[0])

    @pyqtSlot()
    def on_pushButton_2_clicked(self, quiet=False):
        """
        Stop
        """
        if self.isRunning():
            if (not self.workflow) and (not quiet):
                reply = QMessageBox.question(
                    self, "Confirmation",
                    "<p style='line-height:25px; height:25px'>trimAl is still running, terminate it?</p>",
                    QMessageBox.Yes, QMessageBox.Cancel)
            else:
                reply = QMessageBox.Yes
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
                if (not self.workflow) and (not quiet):
                    QMessageBox.information(
                        self, "trimAl",
                        "<p style='line-height:25px; height:25px'>Program has been terminated!</p>"
                    )
                self.startButtonStatusSig.emit([
                    self.pushButton, [self.progressBar], "except",
                    self.dict_args["exportPath"], self.qss_file, self
                ])

    def run_command(self):
        try:
            # 清空文件夹,放在这里方便统一报错
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            ##进度条用
            # self.dict_file_progress = {os.path.basename(file): 0 for file in self.dict_args["seq_files"]}
            if self.radioButton.isChecked():
                async_results = [
                    self.pool.apply_async(run,
                                          args=(self.dict_args, self.command,
                                                file))
                    for file in self.dict_args["inputFiles"]
                ]
                self.totalFileNum = len(self.dict_args["inputFiles"])
            else:
                async_results = [
                    self.pool.apply_async(run,
                                          args=(self.dict_args, self.command,
                                                self.dict_args["compareFile"]))
                ]
                self.totalFileNum = 1
            self.finishedFileNum = 0  #进度条用
            self.pool.close()  # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
            map(ApplyResult.wait, async_results)
            lst_results = [r.get() for r in async_results]
            # 判断比对是否成功
            trimAl_results = glob.glob(self.exportPath + os.sep +
                                       "*_trimAl.fas")
            empty_files = [
                os.path.basename(file) for file in trimAl_results
                if os.stat(file).st_size == 0
            ]
            has_error = False
            if not trimAl_results or empty_files:
                has_error = True
                log = self.textEdit_log.toPlainText()
                list_commands = re.findall(r"Command: (.+)\n", log)
                last_cmd = list_commands[-1] if list_commands else ""
                self.trimAl_exception.emit(
                    "trimAl execute failed, click <span style=\"color:red\">Show log</span> to see details!"
                    "<br>You can also copy this command to terminal to debug: %s"
                    % last_cmd)
            if not has_error:
                self.renameSequence()  #修剪后序列的名字被,需要改回来
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), self.time_used)
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    self.description +
                    "\n\nIf you use PhyloSuite, please cite:\nZhang, D., F. Gao, I. Jakovlić, H. Zou, J. Zhang, W.X. Li, and G.T. Wang, PhyloSuite: An integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. Molecular Ecology Resources, 2020. 20(1): p. 348–355. DOI: 10.1111/1755-0998.13096.\n"
                    "If you use trimAl, please cite:\n" + self.reference +
                    "\n\n" + self.time_used_des)
            if (not self.interrupt) and (not has_error):
                self.pool = None
                self.interrupt = False
                if self.workflow:
                    # work flow跑的
                    self.startButtonStatusSig.emit([
                        self.pushButton, self.progressBar, "workflow stop",
                        self.exportPath, self.qss_file, self
                    ])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop", self.exportPath,
                    self.qss_file, self
                ])
                self.focusSig.emit(self.exportPath)
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])
                self.pool = None
                self.interrupt = False
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            self.pool = None
            self.interrupt = False

    def guiSave(self):
        # Save geometry
        self.trimAl_settings.setValue('size', self.size())
        # self.trimAl_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                index = obj.currentIndex()
                self.trimAl_settings.setValue(name, index)
            elif isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.trimAl_settings.setValue(name, state)
            elif isinstance(obj, QRadioButton):
                state = obj.isChecked()
                self.trimAl_settings.setValue(name, state)
            elif isinstance(obj, QDoubleSpinBox):
                float_ = obj.value()
                self.trimAl_settings.setValue(name, float_)
            elif isinstance(obj, QLineEdit):
                value = obj.text()
                self.trimAl_settings.setValue(name, value)
            elif isinstance(obj, QTabWidget):
                index = obj.currentIndex()
                self.trimAl_settings.setValue(name, index)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.trimAl_settings.value('size', QSize(732, 595)))
        self.factory.centerWindow(self)
        # self.move(self.trimAl_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox_6":
                    cpu_num = multiprocessing.cpu_count()
                    list_cpu = [str(i + 1) for i in range(cpu_num)]
                    index = self.trimAl_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(list_cpu):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
                elif name == "comboBox_4":
                    self.input(self.autoInputs)
                else:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    index = self.trimAl_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        item.setToolTip(i)
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
            elif isinstance(obj, QCheckBox):
                value = self.trimAl_settings.value(
                    name, "no setting")  # get stored value from registry
                if value != "no setting":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            elif isinstance(obj, QRadioButton):
                value = self.trimAl_settings.value(
                    name, "no setting")  # get stored value from registry
                if value != "no setting":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            elif isinstance(obj, QDoubleSpinBox):
                ini_float_ = obj.value()
                float_ = self.trimAl_settings.value(name, ini_float_)
                obj.setValue(float(float_))
            elif isinstance(obj, QLineEdit):
                if name not in ["lineEdit_3", "lineEdit_2"]:
                    value = self.trimAl_settings.value(
                        name, "")  # get stored value from registry
                    if value:
                        obj.setText(value)  # restore checkbox
            elif isinstance(obj, QTabWidget):
                index = self.trimAl_settings.value(name, 0)
                obj.setCurrentIndex(int(index))

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.guiSave()
        self.log_gui.close()  # 关闭子窗口
        self.closeSig.emit("trimAl", self.fetchWorkflowSetting())
        # 断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("trimAl")
            # 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            reply = QMessageBox.question(
                self, "trimAl",
                "<p style='line-height:25px; height:25px'>trimAl is still running, terminate it?</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
            else:
                event.ignore()

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        name = obj.objectName()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                self.input(files)
        if isinstance(obj, QLineEdit):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                if name == "lineEdit_2":
                    base = os.path.basename(files[0])
                    self.lineEdit_2.setText(base)
                    self.lineEdit_2.setToolTip(files[0])
                elif name == "lineEdit_3":
                    base = os.path.basename(files[0])
                    self.lineEdit_3.setText(base)
                    self.lineEdit_3.setToolTip(files[0])
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # return QMainWindow.eventFilter(self, obj, event) #
        # 其他情况会返回系统默认的事件处理方法。
        return super(TrimAl, self).eventFilter(obj, event)  # 0

    def gui4Log(self):
        dialog = QDialog(self)
        dialog.resize(800, 500)
        dialog.setWindowTitle("Log")
        gridLayout = QGridLayout(dialog)
        horizontalLayout_2 = QHBoxLayout()
        label = QLabel(dialog)
        label.setText("Log of trimAl:")
        horizontalLayout_2.addWidget(label)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        horizontalLayout_2.addItem(spacerItem)
        toolButton = QToolButton(dialog)
        icon2 = QIcon()
        icon2.addPixmap(
            QPixmap(
                ":/picture/resourses/interface-controls-text-wrap-512.png"))
        toolButton.setIcon(icon2)
        toolButton.setCheckable(True)
        toolButton.setToolTip("Use Wraps")
        toolButton.clicked.connect(self.setWordWrap)
        toolButton.setChecked(True)
        horizontalLayout_2.addWidget(toolButton)
        pushButton = QPushButton("Save to file", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
        pushButton.setIcon(icon)
        pushButton_2 = QPushButton("Close", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png"))
        pushButton_2.setIcon(icon)
        self.textEdit_log = QTextEdit(dialog)
        self.textEdit_log.setReadOnly(True)
        gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2)
        gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2)
        gridLayout.addWidget(pushButton, 2, 0, 1, 1)
        gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
        pushButton.clicked.connect(self.save_log_to_file)
        pushButton_2.clicked.connect(dialog.close)
        dialog.setWindowFlags(dialog.windowFlags()
                              | Qt.WindowMinMaxButtonsHint)
        return dialog

    def addText2Log(self, text):
        if re.search(r"\w+", text):
            self.textEdit_log.append(text)
            with open(self.exportPath + os.sep + "PhyloSuite_TrimAl_.log",
                      "a") as f:
                f.write(text + "\n")

    def save_log_to_file(self):
        content = self.textEdit_log.toPlainText()
        fileName = QFileDialog.getSaveFileName(self, "trimAl", "log",
                                               "text Format(*.txt)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(content)

    def setWordWrap(self):
        button = self.sender()
        if button.isChecked():
            button.setChecked(True)
            self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            button.setChecked(False)
            self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap)

    def input(self, list_items=None):
        if list_items:
            self.comboBox_4.refreshInputs(list_items)
        else:
            self.comboBox_4.refreshInputs([])

    def showCMD(self):
        """
        show command
        """
        self.command = self.fetchCommands()
        if self.command:
            dialog = QDialog(self)
            dialog.resize(600, 200)
            dialog.setWindowTitle("Command")
            gridLayout = QGridLayout(dialog)
            label = QLabel(dialog)
            label.setText("Current Command:")
            pushButton = QPushButton("Save and run", dialog)
            icon = QIcon()
            icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
            pushButton.setIcon(icon)
            pushButton_2 = QPushButton("Close", dialog)
            icon = QIcon()
            icon.addPixmap(
                QPixmap(":/picture/resourses/if_Delete_1493279.png"))
            pushButton_2.setIcon(icon)
            self.textEdit_cmd = QTextEdit(dialog)
            self.textEdit_cmd.setText(self.command)
            self.textEdit_cmd.textChanged.connect(self.judgeCmdText)
            gridLayout.addWidget(label, 0, 0, 1, 2)
            gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2)
            gridLayout.addWidget(pushButton, 2, 0, 1, 1)
            gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
            pushButton.clicked.connect(lambda: [
                self.run_with_CMD(self.textEdit_cmd.toPlainText()),
                dialog.close()
            ])
            pushButton_2.clicked.connect(dialog.close)
            dialog.setWindowFlags(dialog.windowFlags()
                                  | Qt.WindowMinMaxButtonsHint)
            dialog.exec_()

    def clear_lineEdit(self):
        sender = self.sender()
        lineEdit = sender.parent()
        lineEdit.setText("")
        lineEdit.setToolTip("")

    def fetchCommands(self):
        if (self.radioButton.isChecked() and self.comboBox_4.count()) or \
                (self.radioButton_2.isChecked() and self.lineEdit_2.toolTip()):
            self.interrupt = False
            self.error_has_shown = False
            self.dict_args = {}
            self.dict_args["workPath"] = self.workPath
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + \
                                                      os.sep + "trimAl_results" + os.sep + self.output_dir_name)
            self.dict_args["exportPath"] = self.exportPath
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                # 提醒是否删除旧结果,如果用户取消,就不执行
                return
            if self.tabWidget.tabText(
                    self.tabWidget.currentIndex()) == "Automated Trimming":
                # 自动模式
                method = self.getAutoMethod()
                self.dict_args["autoMethod"] = " -%s" % method
                self.dict_args["cons"] = ""
                self.dict_args["gt"] = ""
                self.dict_args["st"] = ""
                self.dict_args["ct"] = ""
                self.dict_args["w"] = ""
                self.dict_args["gw"] = ""
                self.dict_args["sw"] = ""
                self.dict_args["cw"] = ""
            else:
                # 人工模式
                self.dict_args["autoMethod"] = ""
                self.dict_args[
                    "cons"] = " -cons %.1f" % self.doubleSpinBox.value()
                self.dict_args[
                    "gt"] = " -gt %.1f" % self.doubleSpinBox_2.value()
                self.dict_args[
                    "st"] = " -st %.1f" % self.doubleSpinBox_3.value()
                self.dict_args[
                    "ct"] = " -ct %.1f" % self.doubleSpinBox_4.value(
                    ) if self.checkBox_9.isChecked() else ""
                self.dict_args["w"] = " -w %.1f" % self.doubleSpinBox_7.value(
                ) if self.checkBox_3.isChecked() else ""
                self.dict_args[
                    "gw"] = " -gw %.1f" % self.doubleSpinBox_6.value(
                    ) if self.checkBox_6.isChecked() else ""
                self.dict_args[
                    "sw"] = " -sw %.1f" % self.doubleSpinBox_5.value(
                    ) if self.checkBox_7.isChecked() else ""
                self.dict_args[
                    "cw"] = " -cw %.1f" % self.doubleSpinBox_8.value(
                    ) if self.checkBox_8.isChecked() else ""
            self.dict_args[
                "complementary"] = " -complementary" if self.checkBox.isChecked(
                ) else ""
            self.dict_args[
                "colnumbering"] = " -colnumbering" if self.checkBox_2.isChecked(
                ) else ""
            html_file_name = self.lineEdit_4.text() if self.lineEdit_4.text(
            ) else "summary.html"
            self.dict_args["htmlout"] = " -htmlout \"%s\"" % (
                self.dict_args["exportPath"] + os.sep + "$fileBase$_" +
                html_file_name) if self.checkBox_4.isChecked() else ""
            self.dict_args["stat"] = " -%s" % self.comboBox_2.currentText(
            ).split(":")[0] if self.checkBox_5.isChecked() else ""
            self.dict_args["outFormat"] = " -%s" % self.comboBox.currentText()
            # 输出文件后缀
            dict_suffix = {
                "fasta": ".fas",
                "phylip": ".phy",
                "phylip3.2": ".phy",
                "nexus": ".nex",
                "mega": ".meg",
                "clustal": ".clw",
                "nbrf": ".nbrf"
            }
            self.dict_args["suffix"] = dict_suffix[self.comboBox.currentText()]
            self.dict_args["trimAl"] = self.TApath
            ##输入文件
            if self.radioButton.isChecked():
                self.dict_args["compareFile"] = ""
                self.dict_args["inputFiles"] = self.comboBox_4.fetchListsText()
                command = "\"{trimAl}\" -in alignment{autoMethod}{cons}{gt}{st}{ct}" \
                          "{w}{gw}{sw}{cw}{complementary}{colnumbering}{htmlout}" \
                          "{outFormat} -out outputFile{stat}".format(**self.dict_args)
            else:
                # compare
                self.dict_args["inputFiles"] = []
                self.dict_args["compareFile"] = self.lineEdit_2.toolTip()
                command = "\"{trimAl}\" -compareset setFile{autoMethod}{cons}{gt}{st}{ct}" \
                          "{w}{gw}{sw}{cw}{complementary}{colnumbering}{htmlout}{outFormat}" \
                          " -out outputFile{stat}".format(**self.dict_args)
            self.reference = "Capella-Gutierrez S, Silla-Martinez JM, Gabaldon T. 2009. trimAl: a tool for automated" \
                             " alignment trimming in large-scale phylogenetic analyses. Bioinformatics. 25: 1972-1973. " \
                             "doi: 10.1093/bioinformatics/btp348."
            cmd_used = "{autoMethod}{cons}{gt}{st}{ct}{w}{gw}{sw}{cw}".format(
                **self.dict_args).strip()
            self.description = "Gap sites were removed with trimAl (Capella‐Gutiérrez et al., 2009) using \"%s\" command." % cmd_used
            self.textEdit_log.clear()  # 清空
            return command
        else:
            if self.radioButton.isChecked():
                QMessageBox.critical(
                    self, "trimAl",
                    "<p style='line-height:25px; height:25px'>Please input files to \"Input\" box first!</p>"
                )
            else:
                QMessageBox.critical(
                    self, "trimAl",
                    "<p style='line-height:25px; height:25px'>Please input files to \"Compare set\" box first!</p>"
                )

    def isRunning(self):
        '''判断程序是否运行,依赖进程是否存在来判断'''
        return hasattr(self, "pool") and self.pool and not self.interrupt

    def run_with_CMD(self, cmd):
        self.command = cmd
        if self.command:
            self.interrupt = False
            self.error_has_shown = False
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            thread = thread if len(
                self.dict_args["inputFiles"]) > thread else len(
                    self.dict_args["inputFiles"])
            thread = 1 if not self.dict_args[
                "inputFiles"] else thread  # compare的情况
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    def judgeCmdText(self):
        text = self.textEdit_cmd.toPlainText()
        if self.radioButton.isChecked():
            if " -in alignment" not in text:
                QMessageBox.information(
                    self, "trimAl",
                    "<p style='line-height:25px; height:25px'>\"-in alignment\" cannot be changed!</p>"
                )
                self.textEdit_cmd.undo()
        else:
            if " -compareset setFile" not in text:
                QMessageBox.information(
                    self, "trimAl",
                    "<p style='line-height:25px; height:25px'>\"-compareset setFile\" cannot be changed!</p>"
                )
                self.textEdit_cmd.undo()

    def updateProcess(self):
        if self.queue.empty(): return
        info = self.queue.get()
        if info[0] == "log":
            message = info[1]
            self.logGuiSig.emit(message)
        elif info[0] == "prog":
            self.finishedFileNum += 1
            if not self.interrupt:
                self.workflow_progress.emit(self.finishedFileNum * 95 /
                                            self.totalFileNum)
                self.progressSig.emit(self.finishedFileNum * 95 /
                                      self.totalFileNum)
        elif info[0] == "popen":
            self.list_pids.append(info[1])
        elif info[0] == "error":
            self.on_pushButton_2_clicked(quiet=True)  #杀掉进程
            self.trimAl_exception.emit(
                "Error happened! Click <span style='font-weight:600; color:#ff0000;'>Show log</span> to see detail!"
            )
            self.error_has_shown = True
        elif info[0] == "popen finished":
            if info[1] in self.list_pids:
                self.list_pids.remove(info[1])

    def popup_trimAl_exception(self, text):
        if not self.error_has_shown:
            QMessageBox.critical(
                self, "trimAl",
                "<p style='line-height:25px; height:25px'>%s</p>" % text)
            if "Show log" in text:
                self.on_pushButton_9_clicked()

    def judgeComboBox_2(self, text):
        if text in ["sfc: Print compare values for columns in the selected alignment from compare files method.",
                    "sft: Print accumulated compare values count for the selected alignment from compare files method."] \
                and self.radioButton.isChecked():
            QMessageBox.information(
                self, "trimAl",
                "<p style='line-height:25px; height:25px'>This option should be used in combination with \"Compare set\".</p>"
            )
            self.comboBox_2.setCurrentIndex(0)

    def switchCheckBox1(self, bool_):
        checkbox = self.sender()
        if (checkbox
                == self.checkBox_2) and bool_ and self.checkBox_5.isChecked():
            self.checkBox_5.setChecked(False)
        if (checkbox
                == self.checkBox_5) and bool_ and self.checkBox_2.isChecked():
            self.checkBox_2.setChecked(False)

    def switchCheckBox2(self, bool_):
        checkbox = self.sender()
        if checkbox == self.checkBox_3:
            if bool_:
                for i in [self.checkBox_6, self.checkBox_7, self.checkBox_8]:
                    i.setChecked(not bool_)
        else:
            if bool_:
                self.checkBox_3.setChecked(not bool_)

    def judgeInput(self, bool_):
        if bool_ and (not self.radioButton_2.isChecked()):
            QMessageBox.information(
                self, "trimAl",
                "<p style='line-height:25px; height:25px'>This option should be used in combination with \"Compare set\".</p>"
            )
            self.checkBox_9.setChecked(False)

    def Un_checkBox_9(self, bool_):
        if bool_:
            self.checkBox_9.setChecked(not bool_)

    def popupAutoDec(self, init=False):
        self.init = init
        self.factory.popUpAutoDetect("trimAl", self.workPath, self.auto_popSig,
                                     self)

    def popupAutoDecSub(self, popupUI):
        if not popupUI:
            if not self.init:
                QMessageBox.warning(
                    self, "Warning",
                    "<p style='line-height:25px; height:25px'>No available file detected!</p>"
                )
            return
        if not self.init: popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(autoInputs)

    def judgeFormats(self, text):
        if text != "fasta":
            QMessageBox.warning(
                self, "HmmCleaner",
                "<p style='line-height:25px; height:25px'>\"%s\" format cannot be used by the downstream programs "
                "(e.g. concatenation), please select fasta format if you are going to use "
                "this result for other functions.</p>" % text)

    def renameSequence(self):
        trimedFiles = glob.glob(self.exportPath + os.sep + "*_trimAl.fas")
        if trimedFiles:
            for num, file in enumerate(trimedFiles):
                with open(file, encoding="utf-8", errors="ignore") as f:
                    content = f.read()
                with open(file, "w", encoding="utf-8") as f1:
                    f1.write(re.sub(r"(>.+?) \d+ bp", "\\1", content))
                self.progressSig.emit(95 + (5 * (num + 1) / len(trimedFiles)))
                self.workflow_progress.emit(95 +
                                            (5 * (num + 1) / len(trimedFiles)))

    def fetchWorkflowSetting(self):
        '''* Alignment Mode
          * Code table(if codon mode)
          * strategy
          * export format'''
        settings = '''<p class="title">***trimAl***</p>'''
        trim_strategy = self.tabWidget.tabText(self.tabWidget.currentIndex())
        settings += '<p>Trimming strategy: <a href="self.trimAl_exe' \
                    ' factory.highlightWidgets(x.tabWidget.tabBar())">%s</a></p>' % trim_strategy
        if trim_strategy == "Automated Trimming":
            # settings += "|--Automated Trimming--|"
            strategy = self.getAutoMethod()
            settings += '<p>Automated method: <a href="self.trimAl_exe ' \
                        'factory.highlightWidgets(x.radioButton_10,x.radioButton_11,x.radioButton_12,' \
                        'x.radioButton_13,x.radioButton_14,x.radioButton_15)">%s</a></p>' % strategy
        else:
            # settings += "|--Manual Trimming--|"
            cons = self.doubleSpinBox.value()
            settings += '<p>Minimum percentage of positions to conserve [0-100]: <a href="self.trimAl_exe ' \
                        'doubleSpinBox.setFocus() doubleSpinBox.selectAll()' \
                        ' factory.highlightWidgets(x.doubleSpinBox)">%s</a></p>' % cons
            gaps = self.doubleSpinBox_2.value()
            settings += '<p>Gap threshold, fraction of positions without gaps in a column [0-1]: <a href="self.trimAl_exe ' \
                        'doubleSpinBox_2.setFocus() doubleSpinBox_2.selectAll()' \
                        ' factory.highlightWidgets(x.doubleSpinBox_2)">%s</a></p>' % gaps
            ss = self.doubleSpinBox_3.value()
            settings += '<p>Similarity threshold, minimum level of residue similarity within a column [0-1]: <a href="self.trimAl_exe ' \
                        'doubleSpinBox_3.setFocus() doubleSpinBox_3.selectAll()' \
                        ' factory.highlightWidgets(x.doubleSpinBox_3)">%s</a></p>' % ss
            if self.checkBox_3.isChecked():
                w = self.doubleSpinBox_7.value()
                settings += '<p>General window size, applied to all stats: <a href="self.trimAl_exe ' \
                            'doubleSpinBox_7.setFocus() doubleSpinBox_7.selectAll()' \
                            ' factory.highlightWidgets(x.doubleSpinBox_7)">%s</a></p>' % w
            if self.checkBox_6.isChecked():
                gw = self.doubleSpinBox_6.value()
                settings += '<p>Window size applied to Gaps: <a href="self.trimAl_exe ' \
                            'doubleSpinBox_6.setFocus() doubleSpinBox_6.selectAll()' \
                            ' factory.highlightWidgets(x.doubleSpinBox_6)">%s</a></p>' % gw
            if self.checkBox_7.isChecked():
                sw = self.doubleSpinBox_5.value()
                settings += '<p>Window size applied to Similarity: <a href="self.trimAl_exe ' \
                            'doubleSpinBox_5.setFocus() doubleSpinBox_5.selectAll()' \
                            ' factory.highlightWidgets(x.doubleSpinBox_5)">%s</a></p>' % sw
            if self.checkBox_8.isChecked():
                cw = self.doubleSpinBox_8.value()
                settings += '<p>Window size applied to Consistency: <a href="self.trimAl_exe ' \
                            'doubleSpinBox_8.setFocus() doubleSpinBox_8.selectAll()' \
                            ' factory.highlightWidgets(x.doubleSpinBox_8)">%s</a></p>' % cw
        thread = self.comboBox_6.currentText()
        settings += '<p>Thread: <a href="self.trimAl_exe comboBox_6.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread
        return settings

    def isFileIn(self):
        return self.comboBox_4.count()

    def controlRefineText(self, bool_):
        if bool_:
            if not self.comboBox_4.count():
                self.comboBox_4.lineEdit().switchColor(
                    "No input files (Try to drag your file(s) and drop here)")
            else:
                self.comboBox_4.lineEdit().switchColor()
        else:
            self.comboBox_4.lineEdit().switchColor()

    def getAutoMethod(self):
        widgets = (self.gridLayout_8.itemAt(i).widget()
                   for i in range(self.gridLayout_8.count()))
        checkedBox = [
            widget for widget in widgets
            if isinstance(widget, QRadioButton) and widget.isChecked()
        ][0]
        return checkedBox.text()
Пример #13
0
class Matrix(QDialog, Ui_Matrix, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    # warning_signal = pyqtSignal(dict)
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    unalignedSig = pyqtSignal(list)
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    ##用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)
    ##用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)

    def __init__(self,
                 files=None,
                 workPath=None,
                 focusSig=None,
                 workflow=False,
                 parent=None):
        super(Matrix, self).__init__(parent)
        self.parent = parent
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        # 保存设置
        if not workflow:
            self.concatenate_settings = QSettings(
                self.thisPath + '/settings/concatenate_settings.ini',
                QSettings.IniFormat)
        else:
            self.concatenate_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini',
                QSettings.IniFormat)
            self.concatenate_settings.beginGroup("Workflow")
            self.concatenate_settings.beginGroup("temporary")
            self.concatenate_settings.beginGroup('Concatenate Sequence')
        # File only, no fallback to registry or or.
        self.concatenate_settings.setFallbacksEnabled(False)
        self.setupUi(self)
        self.files = files
        self.workPath = workPath
        self.focusSig = focusSig if focusSig else pyqtSignal(
            str)  # 为了方便workflow
        self.workflow = workflow
        # 恢复用户的设置
        self.guiRestore()
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        self.groupBox_top_line.setStyleSheet('''QGroupBox{
        border-bottom:none;
        border-right:none;
        border-left:none;
        }
        QGroupBox::title{
        subcontrol-origin: margin;
        subcontrol-position: top left;
        }''')
        self.unalignedSig.connect(self.unaligned)
        self.exception_signal.connect(self.popupException)
        # self.warning_signal.connect(self.popupWarning)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.comboBox_4.installEventFilter(self)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  #自动识别可用的输入
        ##设置拖拽排序
        self.comboBox_4.view().setDragEnabled(True)
        self.comboBox_4.view().setDragDropMode(QAbstractItemView.InternalMove)
        self.comboBox_4.view().setDefaultDropAction(Qt.MoveAction)
        # self.comboBox_4.view().setSelectionMode(QAbstractItemView.MultiSelection)
        self.comboBox_4.view().installEventFilter(self)
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_2.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-7-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        if self.isFileIn():
            self.dict_args = {}
            self.dict_args["parent"] = self
            self.dict_args["exception_signal"] = self.exception_signal
            # self.dict_args["warning_signal"] = self.warning_signal
            self.dict_args["unaligned_signal"] = self.unalignedSig
            self.dict_args["progressSig"] = self.progressSig
            self.dict_args["workflow_progress"] = self.workflow_progress
            self.dict_args["files"] = self.comboBox_4.fetchListsText()
            self.dict_args["workPath"] = self.workPath
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + os.sep +
                                                      "concatenate_results" +
                                                      os.sep +
                                                      self.output_dir_name)
            self.dict_args["exportPath"] = self.exportPath
            self.dict_args["export_phylip"] = self.checkBox.isChecked()
            self.dict_args["export_nex"] = self.checkBox_2.isChecked()
            self.dict_args["export_nexi"] = self.checkBox_3.isChecked()
            self.dict_args["export_nexig"] = self.checkBox_4.isChecked()
            self.dict_args["export_axt"] = self.checkBox_13.isChecked()
            self.dict_args["export_paml"] = self.checkBox_5.isChecked()
            self.dict_args["export_fas"] = self.checkBox_12.isChecked()
            self.dict_args["export_stat"] = self.checkBox_9.isChecked()
            self.dict_args["export_name"] = self.lineEdit.text(
            ) if self.lineEdit.text() else "concatenation"
            if self.groupBox_top_line.isChecked():
                self.dict_args["draw_linear"] = True
                self.dict_args["fig_height"] = self.spinBox_5.value()
                self.dict_args["fig_width"] = self.spinBox_6.value()
                self.dict_args["label_size"] = self.spinBox_7.value()
                self.dict_args["Label_angle"] = self.spinBox_8.value()
                self.dict_args["Label_position"] = self.comboBox.currentText()
                self.dict_args["Label_color"] = self.pushButton_color.text()
            else:
                self.dict_args["draw_linear"] = False
            if True not in list(self.dict_args.values()):
                QMessageBox.critical(
                    self, "Concatenate sequence",
                    "<p style='line-height:25px; height:25px'>Please select output format(s) first!</p>"
                )
                self.checkBox.setChecked(True)
                return
            ##描述,方便worfflow使用
            self.description = ""
            self.reference = ""
            ok = self.factory.remove_dir(self.dict_args["exportPath"],
                                         parent=self)
            if not ok:
                #提醒是否删除旧结果,如果用户取消,就不执行
                return
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()
        else:
            QMessageBox.critical(
                self, "Concatenate sequence",
                "<p style='line-height:25px; height:25px'>Please input files first!</p>"
            )

    def run_command(self):
        try:
            # 先清空文件夹
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            self.seqMatrix = Seq_matrix(**self.dict_args)
            if not self.seqMatrix.ok:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.dict_args["exportPath"], self.qss_file, self
                ])
                return
            if "draw_linear" in self.dict_args and self.dict_args[
                    "draw_linear"]:
                self.dict_args[
                    "partition_file"] = self.seqMatrix.partition_detail
                Partition2fig(**self.dict_args)
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), self.time_used)
            with open(self.dict_args["exportPath"] + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    "If you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n\n"
                    + self.time_used_des)
            if not self.seqMatrix.unaligned and not self.seqMatrix.interrupt:
                if self.workflow:
                    ##work flow跑的
                    self.startButtonStatusSig.emit([
                        self.pushButton, self.progressBar, "workflow stop",
                        self.dict_args["exportPath"], self.qss_file, self
                    ])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop",
                    self.dict_args["exportPath"], self.qss_file, self
                ])
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.dict_args["exportPath"], self.qss_file, self
                ])
            if not self.workflow:
                self.focusSig.emit(self.dict_args["exportPath"])
            self.seqMatrix.interrupt = True
            # self.seqMatrix.supplement()
            # if self.seqMatrix.dist_warning_message:
            #     self.dict_args["warning_signal"].emit(
            #         self.seqMatrix.dist_warning_message)
            # else:
            #     # if self.mafft_interrupt is not True:
            #     self.seqMatrix.concatenate()
            #     if not self.seqMatrix.unaligned and not self.seqMatrix.interrupt:
            #         if self.workflow:
            #             ##work flow跑的
            #             self.startButtonStatusSig.emit(
            #                 [
            #                     self.pushButton,
            #                     self.progressBar,
            #                     "workflow stop",
            #                     self.dict_args["exportPath"],
            #                     self.qss_file,
            #                     self])
            #             self.workflow_finished.emit("finished")
            #             return
            #         self.startButtonStatusSig.emit(
            #             [self.pushButton, self.progressBar, "stop", self.dict_args["exportPath"], self.qss_file, self])
            #     else:
            #         self.startButtonStatusSig.emit(
            #             [self.pushButton, self.progressBar, "except", self.dict_args["exportPath"], self.qss_file,
            #              self])
            #     if not self.workflow:
            #         self.focusSig.emit(self.dict_args["exportPath"])
            #     self.seqMatrix.interrupt = True
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["exportPath"], self.qss_file, self
            ])

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        """
        Stop
        """
        if self.isRunning():
            self.seqMatrix.interrupt = True
            if not self.workflow:
                QMessageBox.information(
                    self, "Concatenate sequence",
                    "<p style='line-height:25px; height:25px'>Concatenation has been terminated!</p>"
                )
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["exportPath"], self.qss_file, self
            ])

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        open files
        """
        files = QFileDialog.getOpenFileNames(
            self,
            "Input Files",
            filter=
            "Supported Format(*.fas *.fasta *.phy *.phylip *.nex *.nxs *.nexus);;"
        )
        if files[0]:
            self.input(files[0])

    def unaligned(self, message):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            "<p style='line-height:25px; height:25px'>Unaligned sequences found, see details</p>"
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText("Unaligned sequences: " + ",".join(message))
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        rgx = re.compile(r'Permission.+?[\'\"](.+\.csv)[\'\"]')
        if rgx.search(exception):
            csvfile = rgx.search(exception).group(1)
            reply = QMessageBox.critical(
                self, "Concatenate sequence",
                "<p style='line-height:25px; height:25px'>Please close '%s' file first!</p>"
                % os.path.basename(csvfile), QMessageBox.Yes,
                QMessageBox.Cancel)
            if reply == QMessageBox.Yes and platform.system().lower(
            ) == "windows":
                os.startfile(csvfile)
        else:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText(
                'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
            )
            msg.setWindowTitle("Error")
            msg.setDetailedText(exception)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.exec_()

    @pyqtSlot(OrderedDict)
    def popupWarning(self, dist_warning):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Information)
        msg.setText(
            "<p style='line-height:25px; height:25px'>Missing genes are replaced with '?' (see details or 'missing_genes.txt')</p>"
        )
        msg.setWindowTitle("Concatenation Warning")
        max_len_taxa = len(max(list(dist_warning), key=len))
        max_len_taxa = max_len_taxa if max_len_taxa > 7 else 7  #要大于species的占位符
        list_detail = ["Species".ljust(max_len_taxa) + " |Missing genes"] + [
            str(i).ljust(max_len_taxa) + " |" + str(dist_warning[i])
            for i in dist_warning
        ]
        # html_detail = "<html>" + "\n".join(list_detail).replace(" ", "&nbsp;") + "</html>"
        msg.setDetailedText("\n".join(list_detail))
        msg.setStandardButtons(QMessageBox.Ok)
        with open(self.dict_args["exportPath"] + os.sep + "missing_genes.txt",
                  "w",
                  encoding="utf-8") as f:
            f.write("\n".join(list_detail))
        msg.exec_()
        # if msg.exec_() == 1024:  # QDialog.Accepted:
        #     self.seqMatrix.concatenate()
        #     if self.workflow:
        #         ##work flow跑的
        #         self.startButtonStatusSig.emit(
        #             [
        #                 self.pushButton,
        #                 self.progressBar,
        #                 "workflow stop",
        #                 self.dict_args["exportPath"],
        #                 self.qss_file,
        #                 self])
        #         self.workflow_finished.emit("finished")
        #         return
        #     self.startButtonStatusSig.emit(
        #         [self.pushButton, self.progressBar, "stop", self.dict_args["exportPath"], self.qss_file, self])
        #     self.focusSig.emit(self.dict_args["exportPath"])
        #     self.seqMatrix.interrupt = True

    def guiSave(self):
        # Save geometry
        self.concatenate_settings.setValue('size', self.size())
        # self.concatenate_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.concatenate_settings.setValue(name, state)
            if isinstance(obj, QLineEdit):
                text = obj.text()
                self.concatenate_settings.setValue(name, text)
            if isinstance(obj, QSpinBox):
                value = obj.value()
                self.concatenate_settings.setValue(name, value)
            if isinstance(obj, QPushButton):
                if name == "pushButton_color":
                    color = obj.palette().color(1)
                    self.concatenate_settings.setValue(name, color.name())
            if isinstance(obj, QComboBox):
                if name == "comboBox":
                    # save combobox selection to registry
                    index = obj.currentIndex()
                    self.concatenate_settings.setValue(name, index)
            if isinstance(obj, QGroupBox):
                state = obj.isChecked()
                self.concatenate_settings.setValue(name, state)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.concatenate_settings.value('size', QSize(500, 500)))
        # self.move(self.concatenate_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox":
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    index = self.concatenate_settings.value(name, "0")
                    if type(index) != str:
                        index = "0"
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
                else:
                    if self.files:
                        self.input(self.files)
                    else:
                        self.input([])
            if isinstance(obj, QCheckBox):
                value = self.concatenate_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QLineEdit):
                text = self.concatenate_settings.value(name, "concatenation")
                obj.setText(text)
            if isinstance(obj, QSpinBox):
                value = self.concatenate_settings.value(name, None)
                if value:
                    obj.setValue(int(value))
            if isinstance(obj, QGroupBox):
                value = self.concatenate_settings.value(
                    name, "true")  # get stored value from registry
                obj.setChecked(
                    self.factory.str2bool(value))  # restore checkbox
            if isinstance(obj, QPushButton):
                if name == "pushButton_color":
                    color = self.concatenate_settings.value(name, "#F9C997")
                    obj.setStyleSheet("background-color:%s" % color)
                    obj.setText(color)
                    obj.clicked.connect(self.changePbColor)

    def closeEvent(self, event):
        self.guiSave()
        self.closeSig.emit("Concatenation", self.fetchWorkflowSetting())
        self.lineEdit.clearFocus()
        self.lineEdit.deselect()
        ###断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("Concatenation")
            ## 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            # print(self.isRunning())
            reply = QMessageBox.question(
                self, "Concatenate sequence",
                "<p style='line-height:25px; height:25px'>Concatenation is still running, terminate it?</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                self.seqMatrix.interrupt = True
            else:
                event.ignore()

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                files = [
                    i for i in files if os.path.splitext(i)[1].upper() in [
                        ".FAS", ".FASTA", ".PHY", ".PHYLIP", ".NEX", ".NXS",
                        ".NEXUS"
                    ]
                ]
                self.input(files)
        if isinstance(obj, QListWidget):
            if event.type() == QEvent.ChildRemoved:
                obj.setDragEnabled(True)
                obj.setDragDropMode(QAbstractItemView.InternalMove)
                obj.setDefaultDropAction(Qt.MoveAction)
                list_inputs = self.comboBox_4.fetchListsText()
                self.comboBox_4.refreshInputs(list_inputs, sort=False)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        return super(Matrix, self).eventFilter(obj, event)  # 0

    def input(self, files):
        empty_files = []
        rest_files = []
        for file in files:
            if os.stat(file).st_size == 0:
                empty_files.append(os.path.basename(file))
            else:
                rest_files.append(file)
        if empty_files:
            if len(empty_files) > 1:
                word1 = "files are"
                word2 = "they will"
            else:
                word1 = "file is"
                word2 = "it will"
            QMessageBox.warning(
                self, "Concatenation",
                "<p style='line-height:25px; height:25px'>%s %s empty, %s be ignored!</p>"
                % (str(empty_files), word1, word2), QMessageBox.Ok)
        self.comboBox_4.refreshInputs(rest_files)

    def isRunning(self):
        if hasattr(self, "seqMatrix") and (not self.seqMatrix.interrupt):
            return True
        else:
            return False

    def popupAutoDec(self):
        popupUI = self.factory.popUpAutoDetect("Concatenation", self.workPath,
                                               self)
        if not popupUI:
            QMessageBox.warning(
                self, "Warning",
                "<p style='line-height:25px; height:25px'>No available file detected!</p>"
            )
            return
        popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(autoInputs)

    def fetchWorkflowSetting(self):
        '''
            export format;
            export name
        '''
        settings = '''<p class="title">***Concatenate Sequence***</p>'''
        list_formats = []
        for checkbox in [
                self.checkBox, self.checkBox_2, self.checkBox_3,
                self.checkBox_4, self.checkBox_5, self.checkBox_9,
                self.checkBox_12, self.checkBox_13
        ]:
            if checkbox.isChecked():
                list_formats.append(checkbox.text())
        formats = ", ".join(list_formats) if list_formats else "None"
        settings += '<p>Export formats: <a href="self.Concatenation_exe ' \
                    'factory.highlightWidgets(x.checkBox,x.checkBox_2,x.checkBox_3,x.checkBox_4,x.checkBox_5,' \
                    'x.checkBox_9,x.checkBox_12,x.checkBox_13)">%s</a></p>'%formats
        export_name = self.lineEdit.text()
        settings += '<p>Export file name: <a href="self.Concatenation_exe lineEdit.setFocus()' \
                    ' lineEdit.selectAll() factory.highlightWidgets(x.lineEdit)">%s</a></p>'%export_name
        return settings

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))
        # self.showSig.emit(self)

    def isFileIn(self):
        return self.comboBox_4.count()

    def changePbColor(self):
        button = self.sender()
        ini_color = button.palette().color(1)
        color = QColorDialog.getColor(QColor(ini_color), self)
        if color.isValid():
            button.setText(color.name())
            button.setStyleSheet("background-color:%s" % color.name())

    @pyqtSlot(str)
    def popupEmptyFileWarning(self, text):
        QMessageBox.warning(
            self, "Concatenation Warning",
            "<p style='line-height:25px; height:25px'>%s!</p>" % text)
Пример #14
0
class MACSE(QDialog, Ui_MACSE, object):
    exception_signal = pyqtSignal(str)  # 定义所有类都可以使用的信号
    progressSig = pyqtSignal(int)  # 控制进度条
    startButtonStatusSig = pyqtSignal(list)
    logGuiSig = pyqtSignal(str)
    workflow_progress = pyqtSignal(int)
    workflow_finished = pyqtSignal(str)
    # 用于输入文件后判断用
    ui_closeSig = pyqtSignal(str)
    # 用于flowchart自动popup combobox等操作
    showSig = pyqtSignal(QDialog)
    closeSig = pyqtSignal(str, str)
    # 比对完有空文件报错
    emptySig = pyqtSignal(str)

    def __init__(self,
                 workPath=None,
                 focusSig=None,
                 workflow=False,
                 java=None,
                 macseEXE=None,
                 autoMFPath=None,
                 parent=None):
        super(MACSE, self).__init__(parent)
        self.parent = parent
        self.workflow = workflow
        self.factory = Factory()
        self.thisPath = self.factory.thisPath
        self.workPath = workPath
        self.focusSig = focusSig
        self.java = java
        self.macseEXE = macseEXE
        self.autoMFPath = autoMFPath
        self.setupUi(self)
        # 保存设置
        if not workflow:
            self.MACSE_settings = QSettings(
                self.thisPath + '/settings/MACSE_settings.ini',
                QSettings.IniFormat)
        else:
            self.MACSE_settings = QSettings(
                self.thisPath + '/settings/workflow_settings.ini',
                QSettings.IniFormat)
            self.MACSE_settings.beginGroup("Workflow")
            self.MACSE_settings.beginGroup("temporary")
            self.MACSE_settings.beginGroup('MACSE')
        # File only, no fallback to registry or or.
        self.MACSE_settings.setFallbacksEnabled(False)
        # 开始装载样式表
        with open(self.thisPath + os.sep + 'style.qss',
                  encoding="utf-8",
                  errors='ignore') as f:
            self.qss_file = f.read()
        self.setStyleSheet(self.qss_file)
        # 恢复用户的设置
        self.guiRestore()
        self.exception_signal.connect(self.popupException)
        self.startButtonStatusSig.connect(self.factory.ctrl_startButton_status)
        self.progressSig.connect(self.runProgress)
        self.logGuiSig.connect(self.addText2Log)
        self.comboBox_4.installEventFilter(self)
        self.comboBox_4.lineEdit().autoDetectSig.connect(
            self.popupAutoDec)  # 自动识别可用的输入
        self.comboBox_5.installEventFilter(self)
        self.comboBox_7.installEventFilter(self)
        self.log_gui = self.gui4Log()
        # self.pushButton_2.clicked.connect(print)
        self.doubleSpinBox_5.valueChanged.connect(self.judgeStopCost)
        self.doubleSpinBox.valueChanged.connect(self.judgeStopCost)
        self.doubleSpinBox_2.valueChanged.connect(self.judgeStoplrCost)
        self.doubleSpinBox_6.valueChanged.connect(self.judgeStoplrCost)
        self.comboBox_5.itemRemovedSig.connect(self.judgeCombobox5)
        self.checkBox_2.toggled.connect(self.controlRefineText)
        self.emptySig.connect(self.popup_MACSE_exception)
        self.interrupt = False
        # 给开始按钮添加菜单
        menu = QMenu(self)
        menu.setToolTipsVisible(True)
        action = QAction(QIcon(":/picture/resourses/terminal-512.png"),
                         "View | Edit command",
                         menu,
                         triggered=self.showCMD)
        self.work_action = QAction(QIcon(":/picture/resourses/work.png"), "",
                                   menu)
        self.work_action.triggered.connect(
            lambda: self.factory.swithWorkPath(self.work_action, parent=self))
        self.dir_action = QAction(QIcon(":/picture/resourses/folder.png"),
                                  "Output Dir: ", menu)
        self.dir_action.triggered.connect(
            lambda: self.factory.set_direct_dir(self.dir_action, self))
        menu.addAction(action)
        menu.addAction(self.work_action)
        menu.addAction(self.dir_action)
        self.pushButton.toolButton.setMenu(menu)
        self.pushButton.toolButton.menu().installEventFilter(self)
        self.factory.swithWorkPath(self.work_action, init=True,
                                   parent=self)  # 初始化一下
        ## brief demo
        self.label_7.clicked.connect(lambda: QDesktopServices.openUrl(
            QUrl(
                "https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#5-3-1-Brief-example"
            )))

    @pyqtSlot()
    def on_pushButton_clicked(self):
        """
        execute program
        """
        self.command = self.fetchCommands()
        if self.command:
            self.interrupt = False
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            if self.dict_args["refine"]:
                thread = thread if len(
                    self.dict_args["alignment"]) > thread else len(
                        self.dict_args["alignment"])
            else:
                thread = thread if len(
                    self.dict_args["seq_files"]) > thread else len(
                        self.dict_args["seq_files"])
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    @pyqtSlot()
    def on_pushButton_3_clicked(self):
        """
        sequence file
        """
        fileNames = QFileDialog.getOpenFileNames(
            self,
            "Input sequence file",
            filter="Fasta Format(*.fas *.fasta *.fa *.fna);;")
        if fileNames[0]:
            self.input(self.comboBox_4, fileNames[0])

    @pyqtSlot()
    def on_pushButton_4_clicked(self):
        """
        less reliable sequence file
        """
        fileNames = QFileDialog.getOpenFileNames(
            self,
            "Input sequence file",
            filter="Fasta Format(*.fas *.fasta *.fa *.fna);;")
        if fileNames[0]:
            self.input(self.comboBox_5, fileNames[0])

    @pyqtSlot()
    def on_pushButton_22_clicked(self):
        """
        alignment
        """
        fileNames = QFileDialog.getOpenFileNames(
            self,
            "Input alignment",
            filter="Fasta Format(*.fas *.fasta *.fa *.fna);;")
        if fileNames[0]:
            self.input(self.comboBox_7, fileNames[0])

    @pyqtSlot()
    def on_pushButton_9_clicked(self):
        """
        show log
        """
        self.log_gui.show()

    @pyqtSlot()
    def on_pushButton_2_clicked(self):
        """
        Stop
        """
        if self.isRunning():
            if not self.workflow:
                reply = QMessageBox.question(
                    self, "Confirmation",
                    "<p style='line-height:25px; height:25px'>MACSE is still running, terminate it?</p>",
                    QMessageBox.Yes, QMessageBox.Cancel)
            else:
                reply = QMessageBox.Yes
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
                if not self.workflow:
                    QMessageBox.information(
                        self, "MACSE",
                        "<p style='line-height:25px; height:25px'>Program has been terminated!</p>"
                    )
                self.startButtonStatusSig.emit([
                    self.pushButton, [self.progressBar], "except",
                    self.dict_args["exportPath"], self.qss_file, self
                ])

    def run_command(self):
        try:
            # 清空文件夹,放在这里方便统一报错
            time_start = datetime.datetime.now()
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "start",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            ##进度条用
            if self.dict_args["refine"]:
                self.dict_file_progress = {
                    os.path.basename(file): 0
                    for file in self.dict_args["alignment"]
                }
                async_results = [
                    self.pool.apply_async(run,
                                          args=(self.dict_args, self.command,
                                                file, num))
                    for num, file in enumerate(self.dict_args["alignment"])
                ]
            else:
                self.dict_file_progress = {
                    os.path.basename(file): 0
                    for file in self.dict_args["seq_files"]
                }
                async_results = [
                    self.pool.apply_async(run,
                                          args=(self.dict_args, self.command,
                                                file, num))
                    for num, file in enumerate(self.dict_args["seq_files"])
                ]
            self.pool.close()  # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
            map(ApplyResult.wait, async_results)
            lst_results = [r.get() for r in async_results]
            # 判断比对是否成功
            macse_NT_results = glob.glob(self.dict_args["exportPath"] +
                                         os.sep + "*_NT.*")
            empty_files = [
                os.path.basename(file) for file in macse_NT_results
                if os.stat(file).st_size == 0
            ]
            has_error = False
            refine_nopt = True if (self.checkBox_2.isChecked()
                                   and self.comboBox_2.currentText() == "NONE"
                                   ) else False  #fefine模式,并且不optimization
            if not macse_NT_results or empty_files:
                log = self.textEdit_log.toPlainText()
                list_commands = re.findall(r"Command: (.+)\n", log)
                last_cmd = list_commands[-1] if list_commands else ""
                if ("StringIndexOutOfBoundsException"
                        in log) and ("-prog refineAlignment" in log):
                    has_error = True
                    self.emptySig.emit(
                        "MACSE executes failed, click <span style=\"color:red\">Show log</span> to see details! "
                        "<br>As you selected \"Refine\", please ensure that your input files are aligned. "
                        "<br>You can also copy this command to terminal to debug: %s"
                        % last_cmd)
                elif not refine_nopt:
                    has_error = True
                    # if self.checkBox_2.isChecked():
                    #     self.emptySig.emit("MACSE executes failed, click <span style=\"color:red\">Show log</span> to see details! "
                    #                        "<br>As you selected \"Refine\", please ensure that your input files are aligned. "
                    #                        "<br>You can also copy this command to terminal to debug: %s" % last_cmd)
                    # else:
                    self.emptySig.emit(
                        "MACSE execute failed, click <span style=\"color:red\">Show log</span> to see details! "
                        "<br>You can also copy this command to terminal to debug: %s"
                        % last_cmd)
            if not has_error:
                # 替换感叹号和星号,以便下游程序调用
                macse_AA_results = glob.glob(self.dict_args["exportPath"] +
                                             os.sep + "*_AA.*")
                self.replaceSymbols(macse_NT_results + macse_AA_results)
            ##预防optimization为0的时候
            self.progressSig.emit(100)
            self.workflow_progress.emit(100)
            time_end = datetime.datetime.now()
            self.time_used = str(time_end - time_start)
            self.time_used_des = "Start at: %s\nFinish at: %s\nTotal time used: %s\n\n" % (
                str(time_start), str(time_end), self.time_used)
            with open(self.exportPath + os.sep + "summary.txt",
                      "w",
                      encoding="utf-8") as f:
                f.write(
                    self.description +
                    "\n\nIf you use PhyloSuite, please cite:\nZhang, D., Gao, F., Li, W.X., Jakovlić, I., Zou, H., Zhang, J., and Wang, G.T. (2018). PhyloSuite: an integrated and scalable desktop platform for streamlined molecular sequence data management and evolutionary phylogenetics studies. bioRxiv, doi: 10.1101/489088.\n"
                    "If you use MACSE, please cite:\n" + self.reference +
                    "\n\n" + self.time_used_des)
            if (not self.interrupt) and (not has_error):
                self.pool = None
                self.interrupt = False
                if self.workflow:
                    # work flow跑的
                    self.startButtonStatusSig.emit([
                        self.pushButton, self.progressBar, "workflow stop",
                        self.exportPath, self.qss_file, self
                    ])
                    self.workflow_finished.emit("finished")
                    return
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "stop", self.exportPath,
                    self.qss_file, self
                ])
                self.focusSig.emit(self.exportPath)
            else:
                self.startButtonStatusSig.emit([
                    self.pushButton, self.progressBar, "except",
                    self.exportPath, self.qss_file, self
                ])
                self.pool = None
                self.interrupt = False
        except BaseException:
            self.exceptionInfo = ''.join(
                traceback.format_exception(
                    *sys.exc_info()))  # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获
            self.exception_signal.emit(self.exceptionInfo)  # 激发这个信号
            self.startButtonStatusSig.emit([
                self.pushButton, self.progressBar, "except",
                self.dict_args["exportPath"], self.qss_file, self
            ])
            self.pool = None
            self.interrupt = False

    def guiSave(self):
        # Save geometry
        self.MACSE_settings.setValue('size', self.size())
        # self.MACSE_settings.setValue('pos', self.pos())

        for name, obj in inspect.getmembers(self):
            # if type(obj) is QComboBox:  # this works similar to isinstance, but
            # missed some field... not sure why?
            if isinstance(obj, QComboBox):
                # save combobox selection to registry
                index = obj.currentIndex()
                self.MACSE_settings.setValue(name, index)
            if isinstance(obj, QCheckBox):
                state = obj.isChecked()
                self.MACSE_settings.setValue(name, state)
            elif isinstance(obj, QSpinBox):
                value = obj.value()
                self.MACSE_settings.setValue(name, value)
            elif isinstance(obj, QDoubleSpinBox):
                float_ = obj.value()
                self.MACSE_settings.setValue(name, float_)
            elif isinstance(obj, QLineEdit):
                value = obj.text()
                self.MACSE_settings.setValue(name, value)

    def guiRestore(self):

        # Restore geometry
        self.resize(self.MACSE_settings.value('size', QSize(679, 693)))
        # self.move(self.MACSE_settings.value('pos', QPoint(875, 254)))

        for name, obj in inspect.getmembers(self):
            if isinstance(obj, QComboBox):
                if name == "comboBox_6":
                    cpu_num = multiprocessing.cpu_count()
                    list_cpu = [str(i + 1) for i in range(cpu_num)]
                    index = self.MACSE_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(list_cpu):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
                elif name == "comboBox_4":
                    if self.autoMFPath and (not "_mafft."
                                            in self.autoMFPath[0]):
                        self.input(self.comboBox_4, self.autoMFPath)
                        self.input(self.comboBox_7, [])
                    else:
                        self.input(self.comboBox_4, [])
                elif name == "comboBox_5":
                    self.input(self.comboBox_5, None)
                elif name == "comboBox_7":
                    if self.autoMFPath and ("_mafft." in self.autoMFPath[0]):
                        ## MAFFT的结果导入
                        self.checkBox_2.setChecked(True)
                        self.input(self.comboBox_7, self.autoMFPath)
                        self.input(self.comboBox_4, [])
                    else:
                        self.input(self.comboBox_7, [])
                else:
                    allItems = [obj.itemText(i) for i in range(obj.count())]
                    index = self.MACSE_settings.value(name, "0")
                    model = obj.model()
                    obj.clear()
                    for num, i in enumerate(allItems):
                        item = QStandardItem(i)
                        # 背景颜色
                        if num % 2 == 0:
                            item.setBackground(QColor(255, 255, 255))
                        else:
                            item.setBackground(QColor(237, 243, 254))
                        model.appendRow(item)
                    obj.setCurrentIndex(int(index))
            if isinstance(obj, QCheckBox):
                value = self.MACSE_settings.value(
                    name, "no setting")  # get stored value from registry
                if value != "no setting":
                    obj.setChecked(
                        self.factory.str2bool(value))  # restore checkbox
            elif isinstance(obj, QSpinBox):
                ini_value = obj.value()
                value = self.MACSE_settings.value(name, ini_value)
                obj.setValue(int(value))
            elif isinstance(obj, QDoubleSpinBox):
                ini_float_ = obj.value()
                float_ = self.MACSE_settings.value(name, ini_float_)
                obj.setValue(float(float_))
            elif isinstance(obj, QLineEdit):
                value = self.MACSE_settings.value(
                    name, "")  # get stored value from registry
                if value:
                    obj.setText(value)  # restore checkbox

    def runProgress(self, num):
        oldValue = self.progressBar.value()
        done_int = int(num)
        if done_int > oldValue:
            self.progressBar.setProperty("value", done_int)
            QCoreApplication.processEvents()

    def popupException(self, exception):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Critical)
        msg.setText(
            'The program encountered an unforeseen problem, please report the bug at <a href="https://github.com/dongzhang0725/PhyloSuite/issues">https://github.com/dongzhang0725/PhyloSuite/issues</a> or send an email with the detailed traceback to [email protected]'
        )
        msg.setWindowTitle("Error")
        msg.setDetailedText(exception)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def closeEvent(self, event):
        self.guiSave()
        self.log_gui.close()  # 关闭子窗口
        self.closeSig.emit("MACSE", self.fetchWorkflowSetting())
        # 断开showSig和closeSig的槽函数连接
        try:
            self.showSig.disconnect()
        except:
            pass
        try:
            self.closeSig.disconnect()
        except:
            pass
        if self.workflow:
            self.ui_closeSig.emit("MACSE")
            # 自动跑的时候不杀掉程序
            return
        if self.isRunning():
            reply = QMessageBox.question(
                self, "MACSE",
                "<p style='line-height:25px; height:25px'>MACSE is still running, terminate it?</p>",
                QMessageBox.Yes, QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                try:
                    self.worker.stopWork()
                    self.pool.terminate(
                    )  # Terminate all processes in the Pool
                    ## 删除subprocess
                    if platform.system().lower() == "windows":
                        for pid in self.list_pids:
                            os.popen('taskkill /F /T /PID %s' % pid)
                    else:
                        for pid in self.list_pids:
                            os.killpg(os.getpgid(pid), signal.SIGTERM)
                    self.pool = None
                    self.interrupt = True
                except:
                    self.pool = None
                    self.interrupt = True
            else:
                event.ignore()

    def showEvent(self, event):
        QTimer.singleShot(100, lambda: self.showSig.emit(self))

    def eventFilter(self, obj, event):
        # modifiers = QApplication.keyboardModifiers()
        if isinstance(obj, QComboBox):
            if event.type() == QEvent.DragEnter:
                if event.mimeData().hasUrls():
                    # must accept the dragEnterEvent or else the dropEvent
                    # can't occur !!!
                    event.accept()
                    return True
            if event.type() == QEvent.Drop:
                files = [u.toLocalFile() for u in event.mimeData().urls()]
                self.input(obj, files)
        if (event.type()
                == QEvent.Show) and (obj == self.pushButton.toolButton.menu()):
            if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", self.dir_action.text()
                         ) or self.dir_action.text() == "Output Dir: ":
                self.factory.sync_dir(self.dir_action)  ##同步文件夹名字
            menu_x_pos = self.pushButton.toolButton.menu().pos().x()
            menu_width = self.pushButton.toolButton.menu().size().width()
            button_width = self.pushButton.toolButton.size().width()
            pos = QPoint(menu_x_pos - menu_width + button_width,
                         self.pushButton.toolButton.menu().pos().y())
            self.pushButton.toolButton.menu().move(pos)
            return True
        # return QMainWindow.eventFilter(self, obj, event) #
        # 其他情况会返回系统默认的事件处理方法。
        return super(MACSE, self).eventFilter(obj, event)  # 0

    def gui4Log(self):
        dialog = QDialog(self)
        dialog.resize(800, 500)
        dialog.setWindowTitle("Log")
        gridLayout = QGridLayout(dialog)
        horizontalLayout_2 = QHBoxLayout()
        label = QLabel(dialog)
        label.setText("Log of MACSE:")
        horizontalLayout_2.addWidget(label)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        horizontalLayout_2.addItem(spacerItem)
        toolButton = QToolButton(dialog)
        icon2 = QIcon()
        icon2.addPixmap(
            QPixmap(
                ":/picture/resourses/interface-controls-text-wrap-512.png"))
        toolButton.setIcon(icon2)
        toolButton.setCheckable(True)
        toolButton.setToolTip("Use Wraps")
        toolButton.clicked.connect(self.setWordWrap)
        toolButton.setChecked(True)
        horizontalLayout_2.addWidget(toolButton)
        pushButton = QPushButton("Save to file", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
        pushButton.setIcon(icon)
        pushButton_2 = QPushButton("Close", dialog)
        icon = QIcon()
        icon.addPixmap(QPixmap(":/picture/resourses/if_Delete_1493279.png"))
        pushButton_2.setIcon(icon)
        self.textEdit_log = QTextEdit(dialog)
        self.textEdit_log.setReadOnly(True)
        gridLayout.addLayout(horizontalLayout_2, 0, 0, 1, 2)
        gridLayout.addWidget(self.textEdit_log, 1, 0, 1, 2)
        gridLayout.addWidget(pushButton, 2, 0, 1, 1)
        gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
        pushButton.clicked.connect(self.save_log_to_file)
        pushButton_2.clicked.connect(dialog.close)
        dialog.setWindowFlags(dialog.windowFlags()
                              | Qt.WindowMinMaxButtonsHint)
        return dialog

    def addText2Log(self, text):
        if re.search(r"\w+", text):
            self.textEdit_log.append(text)

    def save_log_to_file(self):
        content = self.textEdit_log.toPlainText()
        fileName = QFileDialog.getSaveFileName(self, "MACSE", "log",
                                               "text Format(*.txt)")
        if fileName[0]:
            with open(fileName[0], "w", encoding="utf-8") as f:
                f.write(content)

    def setWordWrap(self):
        button = self.sender()
        if button.isChecked():
            button.setChecked(True)
            self.textEdit_log.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            button.setChecked(False)
            self.textEdit_log.setLineWrapMode(QTextEdit.NoWrap)

    def input(self, combobox, list_items=None):
        if list_items:
            combobox.refreshInputs(list_items)
        else:
            combobox.refreshInputs([])
            if combobox == self.comboBox_5:
                self.comboBox_5.lineEdit().setText("Optional!")
            elif combobox == self.comboBox_7:
                self.comboBox_7.lineEdit().setText(
                    "No input MSA (Improves previously computed alignments)")

    def judgeStopCost(self, value):
        stop_value = self.doubleSpinBox_5.value()
        fs_value = self.doubleSpinBox.value()
        if stop_value >= (2 * fs_value):
            QMessageBox.information(
                self, "MACSE",
                "<p style='line-height:25px; height:25px'>The value of \"stop\" should be less than twice the value of \"fs\".</p>"
            )
            self.doubleSpinBox.setValue(30.0)
            self.doubleSpinBox_5.setValue(50.0)

    def judgeStoplrCost(self, value):
        stop_value = self.doubleSpinBox_6.value()
        fs_value = self.doubleSpinBox_2.value()
        if stop_value >= (2 * fs_value):
            QMessageBox.information(
                self, "MACSE",
                "<p style='line-height:25px; height:25px'>The value of \"stop_lr\" should be less than twice the value of \"fs_lr\".</p>"
            )
            self.doubleSpinBox_2.setValue(10.0)
            self.doubleSpinBox_6.setValue(17.0)

    def fetchCommands(self):
        if self.isFileIn():
            self.interrupt = False
            self.dict_args = {}
            self.dict_args[
                "allowNT"] = " -allow_NT \"%s\"" % self.lineEdit.text(
                ) if self.lineEdit.text() else ""
            self.dict_args[
                "alphabetAA"] = " -alphabet_AA %s" % self.comboBox.currentText(
                )
            self.dict_args[
                "ambiOFF"] = " -ambi_OFF true" if self.checkBox.isChecked(
                ) else ""
            self.dict_args["fs"] = " -fs %.1f" % self.doubleSpinBox.value(
            ) if self.doubleSpinBox.value() != 30.0 else ""
            self.dict_args[
                "fs_lr"] = " -fs_lr %.1f" % self.doubleSpinBox_2.value(
                ) if self.doubleSpinBox_2.value() != 10.0 else ""
            self.dict_args[
                "fs_lr_term"] = " -fs_lr_term %.1f" % self.doubleSpinBox_3.value(
                ) if self.doubleSpinBox_3.value() != 7.0 else ""
            self.dict_args[
                "fs_term"] = " -fs_term %.1f" % self.doubleSpinBox_4.value(
                ) if self.doubleSpinBox_4.value() != 10.0 else ""
            self.dict_args[
                "gap_ext"] = " -gap_ext %.1f" % self.doubleSpinBox_7.value(
                ) if self.doubleSpinBox_7.value() != 1.0 else ""
            self.dict_args[
                "gap_ext_term"] = " -gap_ext_term %.1f" % self.doubleSpinBox_9.value(
                ) if self.doubleSpinBox_9.value() != 0.9 else ""
            self.dict_args[
                "gap_op"] = " -gap_op %.1f" % self.doubleSpinBox_8.value(
                ) if self.doubleSpinBox_8.value() != 7.0 else ""
            self.dict_args[
                "gap_op_term"] = " -gap_op_term %.1f" % self.doubleSpinBox_10.value(
                ) if self.doubleSpinBox_10.value() != 6.3 else ""
            self.dict_args[
                "local_realign_dec"] = " -local_realign_dec %.1f" % self.doubleSpinBox_11.value(
                ) if self.doubleSpinBox_11.value() != 0.5 else ""
            self.dict_args[
                "local_realign_init"] = " -local_realign_init %.1f" % self.doubleSpinBox_12.value(
                ) if self.doubleSpinBox_12.value() != 0.5 else ""
            self.dict_args[
                "max_refine_iter"] = " -max_refine_iter %d" % self.spinBox.value(
                ) if self.spinBox.value() != -1 else ""
            self.dict_args["gc_def"] = " -gc_def %s" % str(
                self.comboBox_9.currentText()).split(" ")[0]
            if self.comboBox_2.currentText() == "NONE":
                self.dict_args["optim"] = " -optim 0"
            elif self.comboBox_2.currentText() == "BASIC":
                self.dict_args["optim"] = " -optim 1"
            else:
                self.dict_args["optim"] = ""
            self.dict_args["score_matrix"] = " -score_matrix %s" % str(
                self.comboBox_3.currentText())
            self.dict_args[
                "stop"] = " -stop %.1f" % self.doubleSpinBox_5.value(
                ) if self.doubleSpinBox_5.value() != 50.0 else ""
            self.dict_args[
                "stop_lr"] = " -stop_lr %.1f" % self.doubleSpinBox_6.value(
                ) if self.doubleSpinBox_6.value() != 17.0 else ""
            self.dict_args["java"] = self.java
            self.dict_args["macseEXE"] = self.macseEXE
            self.dict_args["refine"] = self.checkBox_2.isChecked()
            self.dict_args["prog"] = "alignSequences" if not self.dict_args[
                "refine"] else "refineAlignment"
            self.dict_args["algn_cmd"] = " -align alignment" if self.dict_args[
                "refine"] else ""
            self.reference = "Ranwez V, Douzery EJP, Cambon C, Chantret N, Delsuc F. 2018. MACSE v2: Toolkit for the " \
                             "Alignment of Coding Sequences Accounting for Frameshifts and Stop Codons. Mol Biol Evol. " \
                             "35: 2582-2584. doi: 10.1093/molbev/msy159."
            prefix = "The sequences were aligned" if not self.dict_args[
                "refine"] else "The alignments were refined"
            self.description = "%s using the codon-aware program MACSE v. 2.03 (Ranwez et al., 2018), " \
                               "which preserves reading frame and allows incorporation of sequencing errors or " \
                               "sequences with frameshifts."%prefix
            # self.dict_args["exception_signal"] = self.exception_signal
            # self.dict_args["progressSig"] = self.progressSig
            self.dict_args["workPath"] = self.workPath
            self.output_dir_name = self.factory.fetch_output_dir_name(
                self.dir_action)
            self.exportPath = self.factory.creat_dirs(self.workPath + \
                                                     os.sep + "MACSE_results" + os.sep + self.output_dir_name)
            self.dict_args["exportPath"] = self.exportPath
            ok = self.factory.remove_dir(self.exportPath, parent=self)
            if not ok:
                # 提醒是否删除旧结果,如果用户取消,就不执行
                return
            ##拷贝输入文件进去
            self.dict_args["seq_files"] = []
            self.dict_args["seq_lr_files"] = []
            self.dict_args["alignment"] = []
            try:
                for seq_file in self.comboBox_4.fetchListsText():
                    self.dict_args["seq_files"].append(
                        shutil.copy(seq_file, self.exportPath))
                for seq_lr_file in self.comboBox_5.fetchListsText():
                    self.dict_args["seq_lr_files"].append(
                        shutil.copy(seq_lr_file, self.exportPath))
                for alin in self.comboBox_7.fetchListsText():
                    self.dict_args["alignment"].append(
                        shutil.copy(alin, self.exportPath))
            except:
                QMessageBox.information(
                    self, "MACSE",
                    "<p style='line-height:25px; height:25px'>File copying failed, please check your input files!</p>"
                )
                return
            command = "\"{java}\" -jar \"{macseEXE}\" -prog {prog}{allowNT}{alphabetAA}{ambiOFF}{fs}{fs_lr}" \
                            "{fs_lr_term}{fs_term}{gap_ext}{gap_ext_term}{gap_op}{gap_op_term}{local_realign_dec}" \
                            "{local_realign_init}{max_refine_iter}{gc_def}{optim}{score_matrix}{stop}{stop_lr}" \
                            "{algn_cmd} -seq seqFile -seq_lr seqLrFile".format(**self.dict_args)
            self.textEdit_log.clear()  # 清空
            return command
        else:
            if self.checkBox_2.isChecked():
                QMessageBox.critical(
                    self, "MACSE",
                    "<p style='line-height:25px; height:25px'>Please input files to \"Refine\" box first!</p>"
                )
            else:
                QMessageBox.critical(
                    self, "MACSE",
                    "<p style='line-height:25px; height:25px'>Please input files to \"Seq.\" box first!</p>"
                )

    @pyqtSlot(str, result=bool)
    def fetchPopen(self, command):
        self.MS_popen = self.factory.init_popen(command)
        return True

    def showCMD(self):
        """
        show command
        """
        self.command = self.fetchCommands()
        if self.command:
            dialog = QDialog(self)
            dialog.resize(600, 200)
            dialog.setWindowTitle("Command")
            gridLayout = QGridLayout(dialog)
            label = QLabel(dialog)
            label.setText("Current Command:")
            pushButton = QPushButton("Save and run", dialog)
            icon = QIcon()
            icon.addPixmap(QPixmap(":/picture/resourses/Save-icon.png"))
            pushButton.setIcon(icon)
            pushButton_2 = QPushButton("Close", dialog)
            icon = QIcon()
            icon.addPixmap(
                QPixmap(":/picture/resourses/if_Delete_1493279.png"))
            pushButton_2.setIcon(icon)
            self.textEdit_cmd = QTextEdit(dialog)
            self.textEdit_cmd.setText(self.command)
            self.textEdit_cmd.textChanged.connect(self.judgeCmdText)
            gridLayout.addWidget(label, 0, 0, 1, 2)
            gridLayout.addWidget(self.textEdit_cmd, 1, 0, 1, 2)
            gridLayout.addWidget(pushButton, 2, 0, 1, 1)
            gridLayout.addWidget(pushButton_2, 2, 1, 1, 1)
            pushButton.clicked.connect(lambda: [
                self.run_with_CMD(self.textEdit_cmd.toPlainText()),
                dialog.close()
            ])
            pushButton_2.clicked.connect(dialog.close)
            dialog.setWindowFlags(dialog.windowFlags()
                                  | Qt.WindowMinMaxButtonsHint)
            dialog.exec_()

    def judgeCombobox5(self):
        if not self.comboBox_5.fetchListsText():
            self.comboBox_5.lineEdit().setText("Optional!")

    def run_with_CMD(self, cmd):
        self.command = cmd
        if self.command:
            self.interrupt = False
            self.list_pids = []
            self.queue = multiprocessing.Queue()
            thread = int(self.comboBox_6.currentText())
            thread = thread if len(
                self.dict_args["seq_files"]) > thread else len(
                    self.dict_args["seq_files"])
            self.pool = multiprocessing.Pool(processes=thread,
                                             initializer=pool_init,
                                             initargs=(self.queue, ))
            # Check for progress periodically
            self.timer = QTimer()
            self.timer.timeout.connect(self.updateProcess)
            self.timer.start(1)
            self.worker = WorkThread(self.run_command, parent=self)
            self.worker.start()

    def judgeCmdText(self):
        text = self.textEdit_cmd.toPlainText()
        judge_text = "%s -seq seqFile -seq_lr seqLrFile" % self.dict_args[
            "algn_cmd"]
        if judge_text not in text:
            QMessageBox.information(
                self, "MACSE",
                "<p style='line-height:25px; height:25px'>\"%s\" cannot be changed!</p>"
                % judge_text)
            self.textEdit_cmd.undo()

    def updateProcess(self):
        if self.queue.empty(): return
        info = self.queue.get()
        if info[0] == "log":
            message = info[1]
            self.logGuiSig.emit(message)
        elif info[0] == "prog":
            flag, file, progress = info
            self.dict_file_progress[file] = progress
            total_progress = 0.95 * (sum(self.dict_file_progress.values()) /
                                     len(self.dict_file_progress))
            self.workflow_progress.emit(total_progress)
            self.progressSig.emit(total_progress)
        elif info[0] == "popen":
            self.list_pids.append(info[1])
        elif info[0] == "popen finished":
            if info[1] in self.list_pids:
                self.list_pids.remove(info[1])

    def isRunning(self):
        '''判断程序是否运行,依赖进程是否存在来判断'''
        return hasattr(self, "pool") and self.pool and not self.interrupt

    def popupAutoDec(self):
        popupUI = self.factory.popUpAutoDetect("MACSE", self.workPath, self)
        if not popupUI:
            QMessageBox.warning(
                self, "Warning",
                "<p style='line-height:25px; height:25px'>No available file detected!</p>"
            )
            return
        popupUI.checkBox.setVisible(False)
        if popupUI.exec_() == QDialog.Accepted:
            widget = popupUI.listWidget_framless.itemWidget(
                popupUI.listWidget_framless.selectedItems()[0])
            autoInputs = widget.autoInputs
            self.input(self.comboBox_4, autoInputs)

    def fetchWorkflowSetting(self):
        '''* Alignment Mode
          * Code table(if codon mode)
          * strategy
          * export format'''
        settings = '''<p class="title">***MACSE (Aligns nucleotide coding sequences)***</p>''' if not \
            self.checkBox_2.isChecked() else '''<p class="title">***MACSE (Improves a previously computed alignment)***</p>'''
        code_table = self.comboBox_9.currentText()
        settings += '<p>Code table: <a href="self.MACSE_exe comboBox_9.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_9)">%s</a></p>' % code_table
        Alphabet_AA = self.comboBox.currentText()
        settings += '<p>Alphabet_AA: <a href="self.MACSE_exe comboBox.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox)">%s</a></p>' % Alphabet_AA
        fs = self.doubleSpinBox.value()
        settings += '<p>fs: <a href="self.MACSE_exe doubleSpinBox.setFocus() doubleSpinBox.selectAll()' \
                    ' factory.highlightWidgets(x.doubleSpinBox)">%s</a></p>' % fs
        stop = self.doubleSpinBox_5.value()
        settings += '<p>stop: <a href="self.MACSE_exe doubleSpinBox_5.setFocus() doubleSpinBox_5.selectAll()' \
                    ' factory.highlightWidgets(x.doubleSpinBox_5)">%s</a></p>' % stop
        local_realign_dec = self.doubleSpinBox_11.value()
        settings += '<p>local_realign_dec: <a href="self.MACSE_exe doubleSpinBox_11.setFocus() doubleSpinBox_11.selectAll()' \
                    ' factory.highlightWidgets(x.doubleSpinBox_11)">%s</a></p>' % local_realign_dec
        local_realign_init = self.doubleSpinBox_12.value()
        settings += '<p>local_realign_init: <a href="self.MACSE_exe doubleSpinBox_12.setFocus() doubleSpinBox_12.selectAll()' \
                    ' factory.highlightWidgets(x.doubleSpinBox_12)">%s</a></p>' % local_realign_init
        max_refine_iter = self.spinBox.value()
        settings += '<p>max_refine_iter: <a href="self.MACSE_exe spinBox.setFocus() spinBox.selectAll()' \
                    ' factory.highlightWidgets(x.spinBox)">%s</a></p>' % max_refine_iter
        optimization = self.comboBox_2.currentText()
        settings += '<p>Optimization: <a href="self.MACSE_exe comboBox_2.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_2)">%s</a></p>' % optimization
        score_matrix = self.comboBox_3.currentText()
        settings += '<p>Score matrix: <a href="self.MACSE_exe comboBox_3.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_3)">%s</a></p>' % score_matrix
        thread = self.comboBox_6.currentText()
        settings += '<p>Thread: <a href="self.MACSE_exe comboBox_6.showPopup()' \
                    ' factory.highlightWidgets(x.comboBox_6)">%s</a></p>' % thread
        return settings

    def isFileIn(self):
        if self.checkBox_2.isChecked():
            return self.comboBox_7.count()
        else:
            return self.comboBox_4.count()

    def controlRefineText(self, bool_):
        if bool_:
            if not self.comboBox_7.count():
                self.comboBox_7.lineEdit().switchColor(
                    "No input MSA (Improves previously computed alignments)")
            else:
                self.comboBox_7.lineEdit().switchColor()
        else:
            self.comboBox_7.lineEdit().switchColor()

    def popup_MACSE_exception(self, text):
        QMessageBox.critical(
            self, "MACSE",
            "<p style='line-height:25px; height:25px'>%s</p>" % text)
        if "Show log" in text:
            self.on_pushButton_9_clicked()

    def replaceSymbols(self, list_results):
        '''
        替换结果里面的!和*
        :return:
        '''
        for num, file in enumerate(list_results):
            with open(file, encoding="utf-8", errors="ignore") as f:
                content = f.read()
            name, ext = os.path.splitext(file)
            with open("%s_removed_chars%s" % (name, ext),
                      "w",
                      encoding="utf-8") as f1:
                f1.write(content.replace("!", "?").replace("*", "?"))
            self.progressSig.emit(95 + (5 * (num + 1) / len(list_results)))
            self.workflow_progress.emit(95 + (5 *
                                              (num + 1) / len(list_results)))