#!/usr/bin/env python # -*- coding: utf-8 -*- # 所有类的工厂类 import glob import traceback import sys import time from src.CustomWidget import DetectItemWidget, DetectPopupGui, AnimationShadowEffect, NoteMessage, \ NoteMessage_option, MyQProgressDialog, Inputbox_message, JobFinishMessageBox import subprocess import pickle import os import re import copy import zipfile import shutil from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QNetworkReply from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest from collections import OrderedDict, Counter import csv import platform from src.preset_values import init_sequence_type, preset_workflow class Factory(QObject, object): def __init__(self, parent=None): super(Factory, self).__init__(parent) thisPath = os.path.dirname(os.path.abspath(os.path.dirname(sys.argv[0]))) #上一级目录 thisPath = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) if not os.path.exists( thisPath + os.sep + "style.qss") else thisPath QSettings.setDefaultFormat(QSettings.IniFormat) self.path_settings = QSettings() self.thisPath = self.path_settings.value("thisPath", thisPath) self.workPlaceSettingPath = self.path_settings.value("current workplace setting path", thisPath) self.settings_ini = QSettings( self.thisPath + '/settings/setting_settings.ini', QSettings.IniFormat) self.settings_ini.setFallbacksEnabled(False) self.dict_autoInputs = None def creat_dir(self, folder): # 创建文件夹 if not os.path.exists(folder): os.mkdir(folder) return folder def creat_dirs(self, folder): # 创建多级文件夹 if not os.path.exists(folder): os.makedirs(folder) # print("mkdir: %s"%folder) # else: # print("exist folder:%s"%folder) return folder def remove_dir(self, path, parent=None, removeRoot=False): # 强制删除路径 # 有parent代表可以提醒 if not os.path.exists(path): return filelist = os.listdir(path) mainwindow_settings = QSettings( self.thisPath + '/settings/mainwindow_settings.ini', QSettings.IniFormat, parent=self) mainwindow_settings.setFallbacksEnabled(False) ifPopRemoveRemind = mainwindow_settings.value( "ifPopRemoveRemind", "true") clear_results_bool = mainwindow_settings.value("clear_results", "yes") if self.str2bool(ifPopRemoveRemind) and parent and filelist: icon = ":/picture/resourses/msg_info.png" message = "Previous results found in \"%s\", you can choose to:" % ( os.path.basename(path)) remindinfo = "Do not remind again (also apply to other analyses) " windInfo = NoteMessage_option( message, icon, checkboxText=remindinfo, parent=parent) windInfo.checkBox.clicked.connect( lambda bool_: mainwindow_settings.setValue("ifPopRemoveRemind", not bool_)) if self.str2bool(clear_results_bool): windInfo.clearResults.setChecked(True) else: windInfo.retainResults.setChecked(True) windInfo.clearResults.toggled.connect( lambda bool_: mainwindow_settings.setValue("clear_results", bool_)) if windInfo.exec_() != QDialog.Accepted: return False else: if windInfo.retainResults.isChecked(): # 如果用户选择保留结果,就不删除文件继续运行 return True else: if not self.str2bool(clear_results_bool): # 如果用户不显示删除文件的弹窗,并且用户选择的保留结果文件 return True self.remove_dir_directly(path, removeRoot) # try: # for f in filelist: # filepath = os.path.join(path, f) # if os.path.isfile(filepath): # os.remove(filepath) # #print(filepath+" removed!") # elif os.path.isdir(filepath): # shutil.rmtree(filepath, True) # #print("dir "+filepath+" removed!") # if removeRoot: # shutil.rmtree(path) # except: # pass return True def remove_dir_directly(self, path, removeRoot=False): filelist = os.listdir(path) try: for f in filelist: filepath = os.path.join(path, f) if os.path.isfile(filepath): os.remove(filepath) # print(filepath+" removed!") elif os.path.isdir(filepath): shutil.rmtree(filepath, True) # print("dir "+filepath+" removed!") if removeRoot: shutil.rmtree(path) except: pass def saveArgs(self, path, arg, filename): with open(path + os.sep + "%s.args" % filename, "wb", encoding="utf-8") as lginfile: pickle.dump(arg, lginfile) def readArgs(self, path, filename): file = path + os.sep + "%s.args" % filename if os.path.isfile(file): with open(file, "rb", encoding="utf-8") as pfile: return pickle.load(pfile) else: return None def read_file(self, file): # 解决读取文件的时候的编码问题 try: f = open(file, encoding="utf-8", errors='ignore') line = f.readline() except UnicodeDecodeError: # try: # f = codecs.open(file, encoding="utf-8-sig", errors='ignore') # 转为utf-8的无BOM格式 # line = f.readline() # except UnicodeDecodeError: f = open(file, encoding="gbk", errors='ignore') try: line = f.readline() except UnicodeDecodeError: # messagebox.showerror( # "UnicodeDecodeError", # "Undefined encoding!", # parent=self.root) pass f.seek(0, 0) # 回到起始读取位置 return f def rmvGB(self, IDs, contents): # 将指定ID的序列从gb文件删除 for i in IDs: rgx = re.compile(r"(?s)LOCUS %s.+?//\s*?(?=LOCUS|$)" % i) contents = rgx.sub("", contents) return contents def extract(self, IDs, contents, advance=False, findRepeat=False, findValidated=False): gbExtract = "" restContents = contents restIDs = copy.deepcopy(IDs) extractIDs = [] for i in IDs: rgx = re.compile(r"(?s)LOCUS %s.+?//\s*?(?=LOCUS|$)" % i) # rgxSub = re.compile(r"(?s)LOCUS %s.+?//\s*?(?=LOCUS|$)" % i) if rgx.search(contents): gbExtract += rgx.search(contents).group() + "\n\n" restContents = rgx.sub("", restContents) restIDs.remove(i) extractIDs.append(i) if advance: return gbExtract, restContents, restIDs elif findRepeat: return gbExtract, restContents, extractIDs elif findValidated: return restIDs, extractIDs else: return gbExtract # 替换正则pattern里面的特殊字符 def repPattern(self, pattern): for i in "[]*.?+$^(){}|\/": pattern = pattern.replace(i, "[" + i + "]") return pattern def str2bool(self, v): if type(v) == bool: return v elif type(v) == str: return v.lower() in ("yes", "true", "t", "1", "2", "3") elif type(v) == int: return v > 0 else: return False def refineName(self, name, remain_words="", limit=None): # 替换名字的不识别符号 if not remain_words: fefined_name = re.sub(r"\W", "_", name) else: # 用户指定的字母 p = "\\" + "\\".join(list(remain_words)) fefined_name = re.sub( r'[^a-zA-Z0-9_%s]'%p, '_', name) # remain if limit and len(fefined_name) > limit: # 存文件名字太长的时候会报错,limit即字数限制 fefined_name = fefined_name[:limit - 1] return fefined_name def sort_error_report(self, content): list_content = sorted(content.split("\n")) return "\n".join(list_content).lstrip() def delete_all_files(self, path): # 删除路径下所有文件,留下folder folder_path = [] for i in os.walk(path): folder_path.append(i[0].replace("/", "\\")) # 有文件时 if i[-1] != []: for j in i[-1]: os.remove(i[0] + os.sep + j) return folder_path def unZip(self, zip, target, errorSig): # 如果遇到2个不同文件夹,有同一个名字的文件,就麻烦,比如都有mafft.exe try: input_path = os.path.dirname( zip) if os.path.dirname( zip) else '.' file_zip = zipfile.ZipFile(zip, 'r') namelist = file_zip.namelist() self.topFolder = namelist[0].strip("/") self.exe_file = "" list_target = [target] if "mrbayes" not in target else target.split(" ") #贝叶斯有2种名字 for file in file_zip.namelist(): file_zip.extract(file, input_path) if (os.path.basename(file) in list_target) and os.path.isfile(input_path + os.sep + file): self.exe_file = file file_zip.close() return self.topFolder, self.exe_file except: errorSig.emit(''.join( traceback.format_exception(*sys.exc_info())) + "\n") def ctrl_startButton_status(self, list_): button, progressbar, state, path, qss, parent = list_ if state == "start": button.setEnabled(False) # 使之失效 button.setStyleSheet( 'QPushButton {color: red; background-color: rgb(219, 217, 217)}') button.setText("Running...") elif state == "except": button.setEnabled(True) button.setStyleSheet(qss) button.setText("Start") if type(progressbar) == list: for i in progressbar: i.setProperty("value", 0) else: progressbar.setProperty("value", 0) elif state == "simple stop": button.setEnabled(True) button.setStyleSheet(qss) button.setText("Start") elif state == "workflow stop": button.setStyleSheet(qss) button.setText("Start") elif state == "stop": button.setEnabled(True) button.setStyleSheet(qss) button.setText("Start") dict_ = { "Extraction": ["Open the results folder", "Import the results to MAFFT", "Import the results to MACSE (for CDS)"], "MAFFT": ["Open the results folder", "Import the results to MACSE (for CDS)", "Import the results to Gblocks", "Import the results to trimAl", "Import the results to HmmCleaner", "Import the results to Convert Sequence Format", "Import the results to Concatenate Sequence"], "MACSE": ["Open the results folder", "Import the results to Gblocks", "Import the results to trimAl", "Import the results to HmmCleaner", "Import the results to Convert Sequence Format", "Import the results to Concatenate Sequence"], "Concatenation": ["Open the results folder", "Import the results to Gblocks", "Import the results to trimAl", "Import the results to HmmCleaner", "Import the results to IQ-TREE", "Import the results to ModelFinder", "Import the results to PartitionFinder2"], "PartitionFinder2": ["Open the results folder", "Import the results to IQ-TREE", "Import the results to MrBayes"], "ModelFinder": ["Open the results folder", "Import the results to IQ-TREE", "Import the results to MrBayes"], "Gblocks": ["Open the results folder", "Import the results to Convert Sequence Format", "Import the results to Concatenate Sequence"], "trimAl": ["Open the results folder", "Import the results to Convert Sequence Format", "Import the results to Concatenate Sequence"], "HmmCleaner": ["Open the results folder", "Import the results to Convert Sequence Format", "Import the results to Concatenate Sequence"] } if hasattr(parent, "function_name") and (parent.function_name in dict_): windInfo = JobFinishMessageBox(":/picture/resourses/msg_info.png", parent=parent) windInfo.refreshCombo(dict_[parent.function_name]) if windInfo.exec_() == QDialog.Accepted: selection = windInfo.combox.currentText() if selection == "Open the results folder": self.openPath(path, parent) elif selection == "Import the results to MAFFT": parent.parent.on_Mafft_triggered() elif selection == "Import the results to MACSE (for CDS)": parent.parent.on_MACSE_triggered() elif selection == "Import the results to Gblocks": parent.parent.on_actionGblocks_triggered() elif selection == "Import the results to trimAl": parent.parent.on_actiontrimAl_triggered() elif selection == "Import the results to HmmCleaner": parent.parent.on_actionHmmCleaner_triggered() elif selection == "Import the results to Convert Sequence Format": parent.parent.on_ConvertFMT_triggered() elif selection == "Import the results to Concatenate Sequence": parent.parent.on_Concatenate_triggered() elif selection == "Import the results to ModelFinder": parent.parent.on_actionModelFinder_triggered() elif selection == "Import the results to PartitionFinder2": parent.parent.on_actionPartitionFinder_triggered() elif selection == "Import the results to IQ-TREE": parent.parent.on_actionIQTREE_triggered() elif selection == "Import the results to MrBayes": parent.parent.on_actionMrBayes_triggered() else: reply = QMessageBox.information( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>Task completed! Open the results folder? </p>", QMessageBox.Ok, QMessageBox.Cancel) if reply == QMessageBox.Ok: self.openPath(path, parent) if type(progressbar) == list: for i in progressbar: i.setProperty("value", 0) else: progressbar.setProperty("value", 0) elif state.startswith("extract_no_feature"): button.setEnabled(True) button.setStyleSheet(qss) button.setText("Start") str_ids = state.replace("extract_no_feature", "") num_ids = len(str_ids.split(", ")) msg = QMessageBox(parent) msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) msg.setIcon(QMessageBox.Information) msg.setText( "<p style='line-height:25px; height:25px'>Task completed (%d IDs with no features are ignored, see details)! Open the results folder?</p>"%num_ids) msg.setWindowTitle("Extraction") msg.setDetailedText("No features could be found in these IDs: " + str_ids) reply = msg.exec_() if reply == QMessageBox.Ok: self.openPath(path, parent) if type(progressbar) == list: for i in progressbar: i.setProperty("value", 0) else: progressbar.setProperty("value", 0) else: button.setEnabled(True) button.setStyleSheet(qss) button.setText("Start") msg = QMessageBox(parent) msg.setStandardButtons(QMessageBox.Ok) # msg.setStandardButtons(QMessageBox.Cancel) msg.setIcon(QMessageBox.Question) msg.setText( "<p style='line-height:25px; height:25px'>Task completed with error (see details)! </p>") msg.setWindowTitle("Error") msg.setDetailedText(state) msg.exec_() if type(progressbar) == list: for i in progressbar: i.setProperty("value", 0) else: progressbar.setProperty("value", 0) def ctrl_installButton_status(self, list_): button, table, row, state, qss = list_ startQSS = ''' border-style:none; border:1px solid #B2B6B9; color:red; padding:5px; min-height:15px; border-radius:5px; background-color: rgb(219, 217, 217); background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E1E4E6,stop:1 #CCD3D9); ''' if state == "start": button.setEnabled(False) # 使之失效 button.setStyleSheet(startQSS) button.setText("Installing...") elif state == "cancel": button.setEnabled(True) button.setStyleSheet(qss) button.setIcon( QIcon(":/picture/resourses/Install.png")) button.setText("Install") elif state == "succeed": button.setEnabled(True) button.setStyleSheet(qss) button.setIcon(QIcon(":/picture/resourses/Uninstall.png")) button.setText("Uninstall") table.item(row, 2).setText("Installed") table.item(row, 2).setForeground(QBrush(QColor(57, 173, 0))) elif state == "uninstall": button.setEnabled(True) button.setStyleSheet(qss) table.item(row, 2).setText("Uninstalled") table.item(row, 2).setForeground(QBrush(Qt.red)) button.setText("Install") button.setIcon( QIcon(":/picture/resourses/Install.png")) # button.setIconSize(QSize(25, 25)) elif state == "not need": button.setEnabled(False) button.setStyleSheet(qss) table.item(row, 2).setText("Not needed") table.item(row, 2).setForeground(QBrush(Qt.black)) button.setText("Not needed") def ctrl_file_label(self, label, state): if state == "finished": label.setText("file:") def numbered_Name(self, list_repeat_name_num, name, omit=False): # 如果这个name已经记录在里面了, omit代表为1的时候省略 if ((name + "_copy1") in list_repeat_name_num) or (name in list_repeat_name_num): num = 2 while True: numbered_name = name + "_copy" + str(num) if numbered_name not in list_repeat_name_num: break num += 1 else: numbered_name = name + "_copy1" if not omit else name list_repeat_name_num.append(numbered_name) return numbered_name, list_repeat_name_num def myProgressDialog(self, title, label, busy=False, parent=None, rewrite=False): progressDialog = MyQProgressDialog(parent) if rewrite else QProgressDialog(parent) progressDialog.setWindowFlags( progressDialog.windowFlags() | Qt.WindowMinMaxButtonsHint) progressDialog.setWindowModality(Qt.WindowModal) # 太快了就不打开这个窗口 progressDialog.setMinimumDuration(10) progressDialog.setWindowTitle(title) progressDialog.setLabelText(label) progressDialog.setCancelButtonText("Cancel") progressDialog.canceled.connect(progressDialog.close) # if busy: # progressDialog.setStyleSheet('''QProgressBar{ # min-height:20px; # background:#E1E4E6; # border-radius:5px; # text-align:center; # border:1px solid #E1E4E6; # } # # QProgressBar:chunk{ # border-radius:5px; # background-color:rgb(76, 169, 106); # margin=0.5px; # width=5px; # }''') progressDialog.setMinimumWidth(260) if busy: progressDialog.setMaximum(0) progressDialog.setMaximum(0) return progressDialog def int2word(self, n): """ convert an integer number n into a string of english words """ # break the number into groups of 3 digits using slicing # each group representing hundred, thousand, million, billion, ... ones = ["", "one ", "two ", "three ", "four ", "five ", "six ", "seven ", "eight ", "nine "] tens = ["ten ", "eleven ", "twelve ", "thirteen ", "fourteen ", "fifteen ", "sixteen ", "seventeen ", "eighteen ", "nineteen "] twenties = ["", "", "twenty ", "thirty ", "forty ", "fifty ", "sixty ", "seventy ", "eighty ", "ninety "] thousands = ["", "thousand ", "million ", "billion ", "trillion ", "quadrillion ", "quintillion ", "sextillion ", "septillion ", "octillion ", "nonillion ", "decillion ", "undecillion ", "duodecillion ", "tredecillion ", "quattuordecillion ", "quindecillion", "sexdecillion ", "septendecillion ", "octodecillion ", "novemdecillion ", "vigintillion "] n3 = [] r1 = "" # create numeric string ns = str(n) for k in range(3, 33, 3): r = ns[-k:] q = len(ns) - k # break if end of ns has been reached if q < -2: break else: if q >= 0: n3.append(int(r[:3])) elif q >= -1: n3.append(int(r[:2])) elif q >= -2: n3.append(int(r[:1])) r1 = r # print n3 # test # break each group of 3 digits into # ones, tens/twenties, hundreds # and form a string nw = "" for i, x in enumerate(n3): b1 = x % 10 b2 = (x % 100) // 10 b3 = (x % 1000) // 100 # print b1, b2, b3 # test if x == 0: continue # skip else: t = thousands[i] if b2 == 0: nw = ones[b1] + t + nw elif b2 == 1: nw = tens[b1] + t + nw elif b2 > 1: nw = twenties[b2] + ones[b1] + t + nw if b3 > 0: nw = ones[b3] + "hundred " + nw return nw def write_csv_file(self, path, array, parent, silence=False): try: with open(path, 'w', newline='', encoding="utf-8") as csv_file: writer = csv.writer(csv_file, dialect='excel') # if head: # writer.writerow(head) for row in array: writer.writerow(row) if silence: return QMessageBox.information( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>File saved successfully!</p>") except Exception as exception: rgx = re.compile(r"(?i)Permission.+?[\'\"](.+\.csv)[\'\"]") if (not silence) and rgx.search(str(exception)): csvfile = rgx.search(str(exception)).group(1) reply = QMessageBox.question( parent, "Error", "<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: print("Write an CSV file to path: %s, Case: %s" % (path, exception)) def is_aligned(self, dict_taxon): # 判定序列是否比对过 list_lenth = [] for i in dict_taxon: list_lenth.append(len(dict_taxon[i])) if len(set(list_lenth)) == 1: return True else: return False def is_aligned_file(self, file): # 判定序列是否比对过 dict_taxon = Parsefmt().readfile(file) list_lenth = [] for i in dict_taxon: list_lenth.append(len(dict_taxon[i])) if len(set(list_lenth)) == 1: return True else: return False def fetchAlignmentFile(self, path): list_valid_exts = [ ".FAS", ".FASTA", ".PHY", ".PHYLIP", ".NEX", ".NXS", ".NEXUS"] return [path + os.sep + i for i in os.listdir(path) if os.path.splitext(i)[1].upper() in list_valid_exts] def fetchResuilts(self, rootpath, resuilts): gbPath = rootpath + os.sep + "GenBank_File" otherPath = rootpath + os.sep + "Other_File" gbMatch = [gbPath + os.sep + path1 + os.sep + resuilts for path1 in os.listdir(gbPath) if os.path.exists(gbPath + os.sep + path1 + os.sep + resuilts)] otherMatch = [otherPath + os.sep + path1 + os.sep + resuilts for path1 in os.listdir(otherPath) if os.path.exists(otherPath + os.sep + path1 + os.sep + resuilts)] return gbMatch + otherMatch def fetchSubResults(self, folder): return sorted([folder + os.sep + i for i in os.listdir(folder) if os.path.isdir(folder + os.sep + i)], key=os.path.getmtime, reverse=True) def detectAvailableInputs(self, rootpath, mode=None): gbPath = rootpath + os.sep + "GenBank_File" otherPath = rootpath + os.sep + "Other_File" dict_autoInputs = OrderedDict() # 每一个值的最后一项保存可用文件数目? if mode == "Concatenation": for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = [ subResults + os.sep + i for i in os.listdir(subResults) if (i != "summary.txt") and (not re.search(r"^PhyloSuite\w+\.log$", i))] if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for macsePath in self.fetchResuilts(rootpath, "MACSE_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(macsePath): autoInputs = glob.glob(subResults + os.sep + "*_NT_removed_chars.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(macsePath)] = dict_subResults for gblocksPath in self.fetchResuilts(rootpath, "Gblocks_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(gblocksPath): autoInputs = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() == ".FASTA" and os.path.splitext(i)[0].endswith("_gb")] if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(gblocksPath)] = dict_subResults for trimAlPath in self.fetchResuilts(rootpath, "trimAl_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(trimAlPath): autoInputs = glob.glob(subResults + os.sep + "*_trimAl.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(trimAlPath)] = dict_subResults for HmmCleanerPath in self.fetchResuilts(rootpath, "HmmCleaner_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(HmmCleanerPath): autoInputs = glob.glob(subResults + os.sep + "*_hmm.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(HmmCleanerPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "format conversion": for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = [ subResults + os.sep + i for i in os.listdir(subResults) if i != ("summary.txt") and (not re.search(r"^PhyloSuite\w+\.log$", i))] if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for macsePath in self.fetchResuilts(rootpath, "MACSE_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(macsePath): autoInputs = glob.glob(subResults + os.sep + "*_NT_removed_chars.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(macsePath)] = dict_subResults for gblocksPath in self.fetchResuilts(rootpath, "Gblocks_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(gblocksPath): autoInputs = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() == ".FASTA" and os.path.splitext(i)[0].endswith("_gb")] if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(gblocksPath)] = dict_subResults for trimAlPath in self.fetchResuilts(rootpath, "trimAl_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(trimAlPath): autoInputs = glob.glob(subResults + os.sep + "*_trimAl.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(trimAlPath)] = dict_subResults for HmmCleanerPath in self.fetchResuilts(rootpath, "HmmCleaner_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(HmmCleanerPath): autoInputs = glob.glob(subResults + os.sep + "*_hmm.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(HmmCleanerPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "Gblocks": for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = glob.glob( subResults + os.sep + "*.fas") + glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for macsePath in self.fetchResuilts(rootpath, "MACSE_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(macsePath): autoInputs = glob.glob(subResults + os.sep + "*_NT_removed_chars.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(macsePath)] = dict_subResults for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): autoInputs = glob.glob(subResults + os.sep + "*.fas") +\ glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[ os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "trimAl": for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = glob.glob( subResults + os.sep + "*.fas") + glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for macsePath in self.fetchResuilts(rootpath, "MACSE_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(macsePath): autoInputs = glob.glob(subResults + os.sep + "*_NT_removed_chars.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(macsePath)] = dict_subResults for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): autoInputs = glob.glob(subResults + os.sep + "*.fas") +\ glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[ os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "HmmCleaner": for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = glob.glob( subResults + os.sep + "*.fas") + glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for macsePath in self.fetchResuilts(rootpath, "MACSE_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(macsePath): autoInputs = glob.glob(subResults + os.sep + "*_NT_removed_chars.*") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(macsePath)] = dict_subResults for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): autoInputs = glob.glob(subResults + os.sep + "*.fas") +\ glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[ os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "MAFFT": # 自动导入特殊一些 for extractPath in self.fetchResuilts(rootpath, "extract_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(extractPath): PCG_NUC_files, PCG_AA_files, RNAs_files = [], [], [] if os.path.exists(subResults + os.sep + "CDS_NUC"): PCG_NUC_files = [ subResults + os.sep + "CDS_NUC" + os.sep + i for i in os.listdir( subResults + os.sep + "CDS_NUC")] if os.path.exists(subResults + os.sep + "CDS_AA"): PCG_AA_files = [ subResults + os.sep + "CDS_AA" + os.sep + j for j in os.listdir( subResults + os.sep + "CDS_AA")] if os.path.exists(subResults + os.sep + "RNAs"): RNAs_files.extend([ subResults + os.sep + "RNAs" + os.sep + k for k in os.listdir( subResults + os.sep + "RNAs")]) if os.path.exists(subResults + os.sep + "rRNA"): RNAs_files.extend([ subResults + os.sep + "rRNA" + os.sep + k for k in os.listdir( subResults + os.sep + "rRNA")]) if os.path.exists(subResults + os.sep + "tRNA"): RNAs_files.extend([ subResults + os.sep + "tRNA" + os.sep + k for k in os.listdir( subResults + os.sep + "tRNA")]) if glob.glob(subResults + os.sep + "*.fas"): ##默认提取的情况 autoInputs = glob.glob(subResults + os.sep + "*.fas") dict_subResults[os.path.normpath(subResults)] = autoInputs elif PCG_AA_files or PCG_NUC_files or RNAs_files: autoInputs = (PCG_NUC_files, PCG_AA_files, RNAs_files) dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(extractPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = [os.path.normpath( alignment) for alignment in self.fetchAlignmentFile( otherPath + os.sep + each_path) if os.path.splitext(alignment)[1].upper() in [".FASTA", ".FAS", ".FSA"]] if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "MACSE": # 自动导入特殊一些 for extractPath in self.fetchResuilts(rootpath, "extract_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(extractPath): PCG_NUC_files, CDS_files = [], [] if os.path.exists(subResults + os.sep + "CDS_NUC"): #mitogenome PCG_NUC_files = [ subResults + os.sep + "CDS_NUC" + os.sep + i for i in os.listdir( subResults + os.sep + "CDS_NUC")] if os.path.exists(subResults + os.sep + "CDS"): CDS_files = [ subResults + os.sep + "CDS" + os.sep + i for i in os.listdir( subResults + os.sep + "CDS")] if PCG_NUC_files or CDS_files: autoInputs = PCG_NUC_files + CDS_files dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(extractPath)] = dict_subResults for mafftPath in self.fetchResuilts(rootpath, "mafft_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(mafftPath): autoInputs = glob.glob( subResults + os.sep + "*.fas") + glob.glob(subResults + os.sep + "*.fasta") if autoInputs: dict_subResults[os.path.normpath(subResults)] = autoInputs if dict_subResults: dict_autoInputs[os.path.normpath(mafftPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = [os.path.normpath( alignment) for alignment in self.fetchAlignmentFile( otherPath + os.sep + each_path) if os.path.splitext(alignment)[1].upper() in [".FASTA", ".FAS", ".FSA"]] if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_alignments} elif mode == "PartitionFinder2": for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): if os.listdir(subResults): dict_subResults[ os.path.normpath(subResults)] = subResults if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults elif mode == "ModelFinder": for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): input_files = sorted([subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]], key=lambda x: ["F", "P", "A", "N"].index(os.path.splitext(x)[1][1].upper())) input_file = input_files[0] if input_files else None list_partition = [ subResults + os.sep + i for i in os.listdir(subResults) if i == "partition.txt"] partition_file = list_partition[0] if list_partition else None if input_file or partition_file: dict_subResults[os.path.normpath(subResults)] = [ input_file, partition_file] if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): [list_alignments, None]} elif mode == "IQ-TREE": for concatenatePath in self.fetchResuilts(rootpath, "concatenate_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(concatenatePath): input_files = sorted([subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]], key=lambda x: ["F", "P", "A", "N"].index(os.path.splitext(x)[1][1].upper())) input_MSA = input_files[0] if input_files else None list_partition = [ subResults + os.sep + i for i in os.listdir(subResults) if i == "partition.txt"] partition_file = list_partition[0] if list_partition else "" model = ["CAT", partition_file] if os.path.exists( partition_file) else ["", None] if (model != ["", None]) or input_MSA: dict_subResults[ os.path.normpath(subResults)] = [[input_MSA], model] if dict_subResults: dict_autoInputs[os.path.normpath(concatenatePath)] = dict_subResults for MfPath in self.fetchResuilts(rootpath, "ModelFinder_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(MfPath): list_msa = [] model = ["", None] mf_part_model = None for i in os.listdir(subResults): if "best_scheme.nex" in i: mf_part_model = subResults + os.sep + i elif os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".NXS", ".ALN"]: list_msa.append(subResults + os.sep + i) elif os.path.splitext(i)[1].upper() == ".IQTREE": model = ["MB_normal", subResults + os.sep + i] model = [ "mf_part_model", mf_part_model] if mf_part_model else model input_MSA = list_msa[0] if list_msa else None if (model != ["", None]) or input_MSA: dict_subResults[ os.path.normpath(subResults)] = [[input_MSA], model] if dict_subResults: dict_autoInputs[os.path.normpath(MfPath)] = dict_subResults for PfPath in self.fetchResuilts(rootpath, "PartFind_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(PfPath): input_MSAs = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP"]] input_MSA = input_MSAs[0] if input_MSAs else None path = subResults + os.sep + "analysis" + \ os.sep + "best_scheme.txt" model = ["PF", path] if os.path.exists(path) else ["PF", None] if (model != ["PF", None]) or input_MSA: dict_subResults[ os.path.normpath(subResults)] = [[input_MSA], model] if dict_subResults: dict_autoInputs[os.path.normpath(PfPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[os.path.normpath( otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): [list_alignments, ["", None]]} elif mode == "MrBayes": for MfPath in self.fetchResuilts(rootpath, "ModelFinder_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(MfPath): input_MSAs = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]] input_MSA = input_MSAs[0] if input_MSAs else None list_input_model_file = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() == ".IQTREE"] input_model_file = list_input_model_file[ 0] if list_input_model_file else "" list_part_model = [subResults + os.sep + i for i in os.listdir(subResults) if "best_scheme.nex" in i] input_part_model = list_part_model[ 0] if list_part_model else "" if os.path.exists(input_part_model): f = self.read_file(input_part_model) input_model = f.read() elif os.path.exists(input_model_file): f = self.read_file(input_model_file) model_content = f.read() f.close() rgx_model = re.compile( r"Best-fit model according to.+?\: (.+)") input_model = rgx_model.search(model_content).group(1) else: input_model = None if input_model or input_MSA: dict_subResults[ os.path.normpath(subResults)] = [input_MSA, input_model] if dict_subResults: dict_autoInputs[os.path.normpath(MfPath)] = dict_subResults for PfPath in self.fetchResuilts(rootpath, "PartFind_results"): dict_subResults = OrderedDict() # 按修改时间排序 for subResults in self.fetchSubResults(PfPath): input_MSAs = [subResults + os.sep + i for i in os.listdir(subResults) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP"]] input_MSA = input_MSAs[0] if input_MSAs else None rgx_blk = re.compile(r"(?si)begin mrbayes;(.+)end;") if os.path.exists(subResults + os.sep + "analysis" + os.sep + "best_scheme.txt"): f = self.read_file( subResults + os.sep + "analysis" + os.sep + "best_scheme.txt") scheme = f.read() f.close() input_model = rgx_blk.search(scheme).group( 1).strip().replace("\t", "") else: input_model = None if input_model or input_MSA: dict_subResults[ os.path.normpath(subResults)] = [input_MSA, input_model] if dict_subResults: dict_autoInputs[os.path.normpath(PfPath)] = dict_subResults for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_alignments = self.fetchAlignmentFile( otherPath + os.sep + each_path) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): [list_alignments, None]} elif mode == "parseANNT": for each_path in os.listdir(otherPath): # other file的自动导入 if not os.path.isdir(otherPath + os.sep + each_path): continue list_docx_files = [each_path + os.sep + i for i in os.listdir(otherPath + os.sep + each_path) if os.path.splitext(i)[1].upper() in [".DOCX", ".DOC", ".ODT", ".DOCM", ".DOTX", ".DOTM", ".DOT"]] if list_docx_files: dict_autoInputs[ os.path.normpath(otherPath + os.sep + each_path)] = \ {os.path.normpath(otherPath + os.sep + each_path): list_docx_files} self.dict_autoInputs = dict_autoInputs return dict_autoInputs def judgeAutoInputs(self, mode=None, resultsPath=None): autoInputs = [] if not resultsPath: return autoInputs resultsParentName = os.path.basename(os.path.dirname(resultsPath)) if mode == "Concatenation": if resultsParentName == "mafft_results": autoInputs = [ resultsPath + os.sep + i for i in os.listdir(resultsPath) if i != ("summary.txt") and ((not re.search(r"^PhyloSuite\w+\.log$", i)))] if resultsParentName == "MACSE_results": autoInputs = glob.glob(resultsPath + os.sep + "*_NT_removed_chars.*") if resultsParentName == "Gblocks_results": autoInputs = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() == ".FASTA" and os.path.splitext(i)[0].endswith("_gb")] if resultsParentName == "trimAl_results": autoInputs = glob.glob(resultsPath + os.sep + "*_trimAl.*") if resultsParentName == "HmmCleaner_results": autoInputs = glob.glob(resultsPath + os.sep + "*_hmm.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = list_alignments elif mode == "format conversion": if resultsParentName == "mafft_results": autoInputs = [ resultsPath + os.sep + i for i in os.listdir(resultsPath) if (i != "summary.txt") and (not re.search(r"^PhyloSuite\w+\.log$", i))] if resultsParentName == "MACSE_results": autoInputs = glob.glob(resultsPath + os.sep + "*_NT_removed_chars.*") if resultsParentName == "Gblocks_results": autoInputs = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() == ".FASTA" and os.path.splitext(i)[0].endswith("_gb")] if resultsParentName == "trimAl_results": autoInputs = glob.glob(resultsPath + os.sep + "*_trimAl.*") if resultsParentName == "HmmCleaner_results": autoInputs = glob.glob(resultsPath + os.sep + "*_hmm.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = list_alignments elif mode == "Gblocks": if resultsParentName == "mafft_results": autoInputs = glob.glob( resultsPath + os.sep + "*.fas") + glob.glob(resultsPath + os.sep + "*.fasta") if resultsParentName == "MACSE_results": autoInputs = glob.glob(resultsPath + os.sep + "*_NT_removed_chars.*") if resultsParentName == "concatenate_results": autoInputs = glob.glob(resultsPath + os.sep + "*.fas") + \ glob.glob(resultsPath + os.sep + "*.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = list_alignments elif mode == "trimAl": if resultsParentName == "mafft_results": autoInputs = glob.glob( resultsPath + os.sep + "*.fas") + glob.glob(resultsPath + os.sep + "*.fasta") if resultsParentName == "MACSE_results": autoInputs = glob.glob(resultsPath + os.sep + "*_NT_removed_chars.*") if resultsParentName == "concatenate_results": autoInputs = glob.glob(resultsPath + os.sep + "*.fas") + \ glob.glob(resultsPath + os.sep + "*.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = list_alignments elif mode == "HmmCleaner": if resultsParentName == "mafft_results": autoInputs = glob.glob( resultsPath + os.sep + "*.fas") + glob.glob(resultsPath + os.sep + "*.fasta") if resultsParentName == "MACSE_results": autoInputs = glob.glob(resultsPath + os.sep + "*_NT_removed_chars.*") if resultsParentName == "concatenate_results": autoInputs = glob.glob(resultsPath + os.sep + "*.fas") + \ glob.glob(resultsPath + os.sep + "*.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = list_alignments elif mode == "MAFFT": # 自动导入特殊一些 if resultsParentName == "extract_results": PCG_NUC_files, PCG_AA_files, RNAs_files = [], [], [] if os.path.exists(resultsPath + os.sep + "CDS_NUC"): PCG_NUC_files = [ resultsPath + os.sep + "CDS_NUC" + os.sep + i for i in os.listdir( resultsPath + os.sep + "CDS_NUC")] if os.path.exists(resultsPath + os.sep + "CDS_AA"): PCG_AA_files = [ resultsPath + os.sep + "CDS_AA" + os.sep + j for j in os.listdir( resultsPath + os.sep + "CDS_AA")] if os.path.exists(resultsPath + os.sep + "RNAs"): RNAs_files.extend([ resultsPath + os.sep + "RNAs" + os.sep + k for k in os.listdir( resultsPath + os.sep + "RNAs")]) if os.path.exists(resultsPath + os.sep + "rRNA"): RNAs_files.extend([ resultsPath + os.sep + "rRNA" + os.sep + k for k in os.listdir( resultsPath + os.sep + "rRNA")]) if os.path.exists(resultsPath + os.sep + "tRNA"): RNAs_files.extend([ resultsPath + os.sep + "tRNA" + os.sep + k for k in os.listdir( resultsPath + os.sep + "tRNA")]) if glob.glob(resultsPath + os.sep + "*.fas"): ##默认提取的情况 autoInputs = glob.glob(resultsPath + os.sep + "*.fas") elif PCG_AA_files or PCG_NUC_files or RNAs_files: autoInputs = (PCG_NUC_files, PCG_AA_files, RNAs_files) if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = [os.path.normpath( alignment) for alignment in self.fetchAlignmentFile(resultsPath) if os.path.splitext(alignment)[1].upper() in [".FASTA", ".FAS", ".FSA"]] if list_alignments: autoInputs = list_alignments elif mode == "MACSE": # 自动导入特殊一些 if resultsParentName == "extract_results": PCG_NUC_files, CDS_files = [], [] if os.path.exists(resultsPath + os.sep + "CDS_NUC"): # mitogenome PCG_NUC_files = [ resultsPath + os.sep + "CDS_NUC" + os.sep + i for i in os.listdir( resultsPath + os.sep + "CDS_NUC")] if os.path.exists(resultsPath + os.sep + "CDS"): CDS_files = [ resultsPath + os.sep + "CDS" + os.sep + i for i in os.listdir( resultsPath + os.sep + "CDS")] if PCG_NUC_files or CDS_files: autoInputs = PCG_NUC_files + CDS_files if resultsParentName == "mafft_results": autoInputs = glob.glob( resultsPath + os.sep + "*.fas") + glob.glob(resultsPath + os.sep + "*.fasta") if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = [os.path.normpath( alignment) for alignment in self.fetchAlignmentFile( resultsPath) if os.path.splitext(alignment)[1].upper() in [".FASTA", ".FAS", ".FSA"]] if list_alignments: autoInputs = list_alignments elif mode == "PartitionFinder2": if resultsParentName == "concatenate_results": if os.listdir(resultsPath): autoInputs = resultsPath elif mode == "ModelFinder": if resultsParentName == "concatenate_results": input_files = sorted([resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]], key=lambda x: ["F", "P", "A", "N"].index(os.path.splitext(x)[1][1].upper())) input_file = input_files[0] if input_files else None list_partition = [ resultsPath + os.sep + i for i in os.listdir(resultsPath) if i == "partition.txt"] partition_file = list_partition[0] if list_partition else None if input_file or partition_file: autoInputs = [input_file, partition_file] if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = [list_alignments, None] elif mode == "IQ-TREE": if resultsParentName == "concatenate_results": input_files = sorted([resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]], key=lambda x: ["F", "P", "A", "N"].index(os.path.splitext(x)[1][1].upper())) input_MSA = input_files[0] if input_files else None list_partition = [ resultsPath + os.sep + i for i in os.listdir(resultsPath) if i == "partition.txt"] partition_file = list_partition[0] if list_partition else "" model = ["CAT", partition_file] if os.path.exists( partition_file) else ["", None] if (model != ["", None]) or input_MSA: autoInputs = [[input_MSA], model] if resultsParentName == "ModelFinder_results": list_msa = [] model = ["", None] mf_part_model = None for i in os.listdir(resultsPath): if "best_scheme.nex" in i: mf_part_model = resultsPath + os.sep + i elif os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".NXS", ".ALN"]: list_msa.append(resultsPath + os.sep + i) elif os.path.splitext(i)[1].upper() == ".IQTREE": model = ["MB_normal", resultsPath + os.sep + i] model = [ "mf_part_model", mf_part_model] if mf_part_model else model input_MSA = list_msa[0] if list_msa else None if (model != ["", None]) or input_MSA: autoInputs = [[input_MSA], model] if resultsParentName == "PartFind_results": input_MSAs = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP"]] input_MSA = input_MSAs[0] if input_MSAs else None path = resultsPath + os.sep + "analysis" + \ os.sep + "best_scheme.txt" model = ["PF", path] if os.path.exists(path) else ["PF", None] if (model != ["PF", None]) or input_MSA: autoInputs = [[input_MSA], model] if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = [list_alignments, ["", None]] elif mode == "MrBayes": if resultsParentName == "ModelFinder_results": input_MSAs = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP", ".FAS", ".FASTA", ".NEX", ".NEXUS", ".ALN"]] input_MSA = input_MSAs[0] if input_MSAs else None list_input_model_file = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() == ".IQTREE"] input_model_file = list_input_model_file[ 0] if list_input_model_file else "" list_part_model = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if "best_scheme.nex" in i] input_part_model = list_part_model[ 0] if list_part_model else "" if os.path.exists(input_part_model): f = self.read_file(input_part_model) input_model = f.read() elif os.path.exists(input_model_file): f = self.read_file(input_model_file) model_content = f.read() f.close() rgx_model = re.compile( r"Best-fit model according to.+?\: (.+)") input_model = rgx_model.search(model_content).group(1) else: input_model = None if input_model or input_MSA: autoInputs = [input_MSA, input_model] if resultsParentName == "PartFind_results": input_MSAs = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".PHY", ".PHYLIP"]] input_MSA = input_MSAs[0] if input_MSAs else None rgx_blk = re.compile(r"(?si)begin mrbayes;(.+)end;") if os.path.exists(resultsPath + os.sep + "analysis" + os.sep + "best_scheme.txt"): f = self.read_file( resultsPath + os.sep + "analysis" + os.sep + "best_scheme.txt") scheme = f.read() f.close() input_model = rgx_blk.search(scheme).group( 1).strip().replace("\t", "") else: input_model = None if input_model or input_MSA: autoInputs = [input_MSA, input_model] if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_alignments = self.fetchAlignmentFile(resultsPath) list_alignments = [os.path.normpath( alignment) for alignment in list_alignments if self.is_aligned_file(alignment)] # 只要比对过的 if list_alignments: autoInputs = [list_alignments, None] elif mode == "parseANNT": if os.path.basename(os.path.dirname(resultsPath)) == "Other_File": # 这里的resultsPath类似于Other_File下面的files # other file的自动导入 if os.path.isdir(resultsPath): list_docx_files = [resultsPath + os.sep + i for i in os.listdir(resultsPath) if os.path.splitext(i)[1].upper() in [".DOCX", ".DOC", ".ODT", ".DOCM", ".DOTX", ".DOTM", ".DOT"]] if list_docx_files: autoInputs = list_docx_files if not autoInputs: #["ModelFinder", "IQ-TREE", "MrBayes"]: [list_alignments, None];IQ-TREE:[list_alignments, ["", None]] if mode in ["ModelFinder", "MrBayes"]: autoInputs = [None, None] elif mode == "IQ-TREE": autoInputs = [None, ["", None]] return autoInputs def popUpAutoDetect(self, mode, currentPath, auto_popSig, parent, dict_autoInputs=None): # 先找到rootpath rootPath = currentPath.split("GenBank_File")[0] \ if "GenBank_File" in currentPath else currentPath.split("Other_File")[0] rootPath = rootPath.rstrip(r"/").rstrip(os.sep) if not dict_autoInputs: gbWorker = WorkThread(lambda: self.detectAvailableInputs(rootPath, mode), parent=parent) self.progressDialog_search = self.myProgressDialog( "Please Wait", "Searching for inputs...", busy=True, parent=parent, rewrite=True) self.progressDialog_search.show() self.popupAuto_break = False self.progressDialog_search.canceled.connect(lambda: [setattr(self, "popupAuto_break", True), gbWorker.stopWork]) def work_finish(mode, rootPath, parent, auto_popSig): # self.progressDialog_search.close() # close会调用canceled这个信号,所以重写了QProgressDialog的closeEvent if not self.popupAuto_break: self.popUpAutoDetectSub(mode, rootPath, parent, auto_popSig, self.dict_autoInputs) gbWorker.start() gbWorker.finished.connect(lambda: work_finish(mode, rootPath, parent, auto_popSig)) else: self.popUpAutoDetectSub(mode, rootPath, parent, auto_popSig, dict_autoInputs) def popUpAutoDetectSub(self, mode, rootPath, parent, auto_popSig=None, dict_autoInputs=None): if dict_autoInputs: # 这里造一个带有qlistwidget的dialog popupGui = DetectPopupGui(mode, parent) list_paths = sorted( list(dict_autoInputs), key=lambda x: os.path.basename(x)) for path in list_paths: listItemWidget = DetectItemWidget(path, rootPath, parent, dict_autoInputs[path]) listItemWidget.autoInputs = next(iter(dict_autoInputs[path].items()))[1] # 最近修改的 listwitem = QListWidgetItem(popupGui.listWidget_framless) listwitem.setToolTip( '<br><span style="color: green">★Double-click to open folder</span></body></html> ') listwitem.setSizeHint(listItemWidget.sizeHint()) popupGui.listWidget_framless.addItem(listwitem) popupGui.listWidget_framless.setItemWidget( listwitem, listItemWidget) popupGui.listWidget_framless.setFocus() popupGui.listWidget_framless.item(0).setSelected(True) popupGui.listWidget_framless.setCurrentRow(0) # popupGui.listWidget_framless.itemWidget(popupGui.listWidget_framless.item(0)).setFocus() popupGui.listWidget_framless.itemDoubleClicked.connect(lambda item: self.openPath( popupGui.listWidget_framless.itemWidget(item).pathText, parent)) # 添加最大化按钮 popupGui.setWindowFlags( popupGui.windowFlags() | Qt.WindowMinMaxButtonsHint) self.progressDialog_search.close() # close会调用canceled这个信号,所以重写了QProgressDialog的closeEvent auto_popSig.emit(popupGui) else: self.progressDialog_search.close() auto_popSig.emit(None) def popupViewMenu(self, view, point): if view.indexAt(point).isValid(): view.exec_(QCursor.pos()) def programIsValid(self, program, mode="settings"): # 由存的文件里面读取 if program == "python27": PyPath = self.settings_ini.value('python27', "python") PyPath = "python" if not PyPath else PyPath # 有时候路径为空 try: popen = subprocess.Popen( "%s -V" % PyPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) except: return "uninstall" if mode == "settings" else False stdout = self.getSTDOUT(popen) if re.search(r"python\s+?2\.[3-7]\.", stdout, re.I): # 判断是python 2.3-2.7的版本 self.settings_ini.setValue("python27", PyPath) ##设置一遍,不然workflow报错 return "succeed" if mode == "settings" else PyPath elif program == "RscriptPath": RscriptPath = self.settings_ini.value('RscriptPath', "Rscript") # 有时候路径为空 RscriptPath = "Rscript" if not RscriptPath else RscriptPath # try: # popen = subprocess.Popen( # RscriptPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" if mode == "settings" else False # stdout = self.getSTDOUT(popen) if shutil.which(RscriptPath): #"Usage: /path/to/Rscript [--options]" in stdout: self.settings_ini.setValue("RscriptPath", RscriptPath) ##设置一遍,不然workflow报错 return "succeed" if mode == "settings" else RscriptPath elif program == "mafft": MAFFTpath = self.settings_ini.value('mafft', "") if platform.system().lower() == "linux": #只在linux判断环境变量 if (not MAFFTpath) and shutil.which( "mafft"): return "succeed" if mode == "settings" else "mafft" MAFFTpath = self.getDefaultpluginPath("mafft") if \ not MAFFTpath else MAFFTpath if shutil.which(MAFFTpath): return "succeed" if mode == "settings" else MAFFTpath elif program == "tbl2asn": TSpath = self.settings_ini.value('tbl2asn', "") if (not TSpath) and shutil.which( "tbl2asn"): return "succeed" if mode == "settings" else "tbl2asn" TSpath = os.path.join(self.thisPath, "plugins", "tbl2asn-master", "win.tbl2asn", "tbl2asn.exe") if \ not TSpath else TSpath if shutil.which(TSpath): return "succeed" if mode == "settings" else TSpath elif program == "PF2": PFpath = self.settings_ini.value('PF2', "") PFpath = self.getDefaultpluginPath("PF2") if \ not PFpath else PFpath if PFpath: pf_compiled = PFpath + os.sep + "PartitionFinder.exe" if platform.system().lower() == "windows" else \ PFpath + os.sep + "PartitionFinder" if os.path.exists(PFpath + os.sep + "PartitionFinder.py") or os.path.exists(pf_compiled): return "succeed" if mode == "settings" else PFpath elif program == "gblocks": GBpath = self.settings_ini.value('gblocks', "") if (not GBpath) and shutil.which( "Gblocks"): return "succeed" if mode == "settings" else "Gblocks" GBpath = self.getDefaultpluginPath("gblocks") if \ not GBpath else GBpath if shutil.which(GBpath): return "succeed" if mode == "settings" else GBpath elif program == "iq-tree": IQpath = self.settings_ini.value('iq-tree', "") if (not IQpath) and shutil.which( "iqtree"): return "succeed" if mode == "settings" else "iqtree" IQpath = self.getDefaultpluginPath("iq-tree") if \ not IQpath else IQpath if shutil.which(IQpath): return "succeed" if mode == "settings" else IQpath elif program == "MrBayes": MBpath = self.settings_ini.value('MrBayes', "") if platform.system().lower() == "windows": env_name = "mrbayes_x64" if platform.machine().endswith('64') else "mrbayes_x86" else: env_name = "mb" if (not MBpath) and shutil.which( env_name): return "succeed" if mode == "settings" else env_name MBpath = self.getDefaultpluginPath("MrBayes") if \ not MBpath else MBpath if shutil.which(MBpath): return "succeed" if mode == "settings" else MBpath elif program == "mpi": MPIpath = self.settings_ini.value('mpi', "mpirun") # 有时候路径为空 MPIpath = "mpirun" if not MPIpath else MPIpath if shutil.which(MPIpath): return "succeed" if mode == "settings" else MPIpath # try: # popen = subprocess.Popen( # "%s --version"%MPIpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" if mode == "settings" else False # stdout = "" # while True: # try: # out_line = popen.stdout.readline().decode("utf-8", errors="ignore") # except UnicodeDecodeError: # out_line = popen.stdout.readline().decode("gbk", errors="ignore") # if out_line == "" and popen.poll() is not None: # break # stdout += out_line # if ("Version:" in stdout) and ("Release Date:" in stdout): # return "succeed" if mode == "settings" else MPIpath elif program == "java": javaPath = self.settings_ini.value('java', "java") javaPath = "java" if not javaPath else javaPath # 有时候路径为空 if shutil.which(javaPath): self.settings_ini.setValue("java", javaPath) ##设置一遍,不然workflow报错 return "succeed" if mode == "settings" else javaPath # try: # popen = subprocess.Popen( # "%s -version" % javaPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" if mode == "settings" else False # stdout = self.getSTDOUT(popen) # # print(stdout) # if re.search(r"Runtime Environment.+?\(.+?(\d+?\.\d+?)\.", stdout, re.I): # # 判断是java > 1.5 # version = float(re.search(r"Runtime Environment.+?\(.+?(\d+?\.\d+?)\.", stdout, re.I).group(1)) # if version >= 1.5: # self.settings_ini.setValue("java", javaPath) ##设置一遍,不然workflow报错 # return "succeed" if mode == "settings" else javaPath elif program == "macse": MACSEpath = self.settings_ini.value('macse', "") # 自动判断下是否存在于环境变量 if (not MACSEpath) and shutil.which("macse_v2.03.jar"): return "succeed" if mode == "settings" else "macse_v2.03.jar" MACSEpath = self.getDefaultpluginPath("macse") if \ not MACSEpath else MACSEpath if MACSEpath and os.path.exists(MACSEpath): return "succeed" if mode == "settings" else MACSEpath elif program == "trimAl": trimAlpath = self.settings_ini.value('trimAl', "") # 自动判断下是否存在于环境变量 if (not trimAlpath) and shutil.which("trimal"): return "succeed" if mode == "settings" else "trimal" # try: popen = subprocess.Popen( # "trimal -h", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: pass # stdout = self.getSTDOUT(popen) # if shutil.which("trimal"): # return "succeed" if mode == "settings" else "trimal" trimAlpath = self.getDefaultpluginPath("trimAl") if \ not trimAlpath else trimAlpath if shutil.which(trimAlpath): return "succeed" if mode == "settings" else trimAlpath elif program == "perl": PERLPath = self.settings_ini.value('perl', "perl") PERLPath = "perl" if not PERLPath else PERLPath # 有时候路径为空 # try: # popen = subprocess.Popen( # "%s -version" % PERLPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" if mode == "settings" else False # stdout = self.getSTDOUT(popen) # print(stdout) if shutil.which(PERLPath): self.settings_ini.setValue("perl", PERLPath) ##设置一遍,不然workflow报错 return "succeed" if mode == "settings" else PERLPath elif program == "HmmCleaner": HmmClPath = self.settings_ini.value('HmmCleaner', "HmmCleaner.pl") HmmClPath = "HmmCleaner.pl" if not HmmClPath else HmmClPath # 有时候路径为空 # try: # popen = subprocess.Popen( # "%s --help" % HmmClPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" if mode == "settings" else False # stdout = self.getSTDOUT(popen) if shutil.which(HmmClPath): self.settings_ini.setValue("HmmCleaner", HmmClPath) ##设置一遍,不然workflow报错 return "succeed" if mode == "settings" else HmmClPath return "uninstall" if mode == "settings" else False def programIsValid_2(self, program, path, parent=None, btn=None): # 只有该功能有提醒功能,不能在线程里面使用,专用于settings里面使用 if program == "python27": PyPath = path PyPath = "python" if not PyPath else PyPath # 有时候路径为空 try: popen = subprocess.Popen( "\"%s\" -V" % PyPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) except: return "uninstall" stdout = self.getSTDOUT(popen) if re.search(r"python\s+?2\.[3-7]\.", stdout, re.I): # 判断是python 2.3-2.7的版本 return "succeed" else: if btn == "OK": if PyPath == "python": QMessageBox.critical( parent, "Settings", "<p style='line-height:25px; height:25px'>The path is not validated!</p>") elif parent: QMessageBox.information( parent, "Install Python 2.7", "<p style='line-height:25px; height:25px'>Only Python 2.3-2.7 is allowed, please specify anew!</p>") elif program == "RscriptPath": RscriptPath = path # 有时候路径为空 RscriptPath = "Rscript" if not RscriptPath else RscriptPath # try: # popen = subprocess.Popen( # RscriptPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) if shutil.which(RscriptPath): return "succeed" elif program == "mafft": MAFFTpath = path # ok = self.checkPath(MAFFTpath, parent=parent, allow_space=True) if shutil.which(MAFFTpath): return "succeed" # MAFFTpath = path # try: # popen = subprocess.Popen( # "\"%s\" --version"%MAFFTpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) # if re.search(r"v(\d)\.(\d)", stdout, re.I): # v1, v2 = re.findall(r"v(\d)\.(\d)", stdout, re.I)[0] # if int(v1) < 7 or int(v2) < 3: # QMessageBox.information( # parent, # "Information", # "<p style='line-height:25px; height:25px'>Only MAFFT versions newer than 7.3 is allowed, please specify anew!</p>") # else: # if MAFFTpath and os.path.exists(MAFFTpath): # return "succeed" elif program == "tbl2asn": TSpath = path if shutil.which(TSpath): return "succeed" elif program == "PF2": PFpath = path if PFpath: pf_compiled = PFpath + os.sep + "PartitionFinder.exe" if platform.system().lower() == "windows" else \ PFpath + os.sep + "PartitionFinder" if (os.path.exists(PFpath + os.sep + "PartitionFinder.py") or os.path.exists(pf_compiled)): return "succeed" elif program == "gblocks": GBpath = path if shutil.which(GBpath): return "succeed" elif program == "iq-tree": IQpath = path if shutil.which(IQpath): return "succeed" elif program == "MrBayes": MBpath = path if shutil.which(MBpath): return "succeed" # MBpath = path # try: # popen = subprocess.Popen( # "\"%s\" -v"%MBpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) # if re.search(r"v(\d)\.(\d)", stdout, re.I): # v1, v2 = re.findall(r"v(\d)\.(\d)", stdout, re.I)[0] # if int(v1) < 3 or int(v2) < 2: # QMessageBox.information( # parent, # "Information", # "<p style='line-height:25px; height:25px'>Only MrBayes versions newer than 3.2 is allowed, please specify anew!</p>") # else: # if MBpath and os.path.exists(MBpath): # return "succeed" elif program == "mpi": MPIpath = path # 有时候路径为空 MPIpath = "mpirun" if not MPIpath else MPIpath # try: # popen = subprocess.Popen( # "\"%s\" --version" % MPIpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = "" # while True: # try: # out_line = popen.stdout.readline().decode("utf-8", errors="ignore") # except UnicodeDecodeError: # out_line = popen.stdout.readline().decode("gbk", errors="ignore") # if out_line == "" and popen.poll() is not None: # break # stdout += out_line if shutil.which(MPIpath): return "succeed" elif program == "java": JAVApath = path # 有时候路径为空 JAVApath = "java" if not JAVApath else JAVApath if shutil.which(JAVApath): return "succeed" # try: # popen = subprocess.Popen( # "\"%s\" -version" % JAVApath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) # match = re.search(r"Runtime Environment.+?\(.+?(\d+?\.\d+?)\.", stdout, re.I) # if match and float(match.group(1)) >= 1.5: # # 判断是JAVA 1.5以上的版本 # return "succeed" # else: # if btn == "OK": # if JAVApath == "java": # QMessageBox.critical( # parent, # "Settings", # "<p style='line-height:25px; height:25px'>The path is not validated!</p>") # elif parent: # QMessageBox.information( # parent, # "Install JAVA", # "<p style='line-height:25px; height:25px'>Only JAVA with JRE above 1.5 is allowed, please specify anew!</p>") elif program == "macse": MSpath = path if MSpath and os.path.exists(MSpath): return "succeed" elif program == "trimAl": TApath = path if shutil.which(TApath): return "succeed" elif program == "perl": PERLpath = path # 有时候路径为空 PERLpath = "perl" if not PERLpath else PERLpath # try: # popen = subprocess.Popen( # "\"%s\" -version" % PERLpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) # match = re.search(r"is perl", stdout, re.I) if shutil.which(PERLpath): # 判断是PERL 5以上的版本 return "succeed" # else: # if btn == "OK": # if PERLpath == "perl": # QMessageBox.critical( # parent, # "Settings", # "<p style='line-height:25px; height:25px'>The path is not validated!</p>") # elif parent: # QMessageBox.information( # parent, # "Install PERL", # "<p style='line-height:25px; height:25px'>Only PERL above 5 is allowed, please specify anew!</p>") elif program == "HmmCleaner": HmmClPath = path # 有时候路径为空 HmmClPath = "HmmCleaner.pl" if not HmmClPath else HmmClPath # try: # popen = subprocess.Popen( # "\"%s\" --help" % HmmClPath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) # except: # return "uninstall" # stdout = self.getSTDOUT(popen) # match = re.search(r"Usage:", stdout, re.I) if shutil.which(HmmClPath): return "succeed" return "uninstall" def init_check(self, parent): try: # 判断将mainwindow里面的data的相关设置拷贝到对于workplace的设置里面 data_settings = QSettings( self.workPlaceSettingPath + os.sep + 'data_settings.ini', QSettings.IniFormat) mainwindow_settings = QSettings( self.thisPath + '/settings/mainwindow_settings.ini', QSettings.IniFormat) keys = mainwindow_settings.allKeys() for key in keys: if key.endswith("_displayedArray") or key.endswith("_availableInfo"): path = key.replace("_displayedArray", "").replace("_availableInfo", "") workplace_path = re.sub(r"/|\\", "_", os.path.dirname(self.workPlaceSettingPath)) remainingStr = path.replace(workplace_path, "") if remainingStr.startswith("_GenBank_File"): data_settings.setValue(key, mainwindow_settings.value(key)) mainwindow_settings.remove(key) # 删掉插件不合格的路径 for i in ["python27", "mafft", "PF2", "gblocks", "iq-tree", "MrBayes", "tbl2asn", "RscriptPath", "mpi", "perl", "java", "macse", "trimAl", "HmmCleaner"]: path = self.settings_ini.value(i, "") if path and not os.path.exists(path): self.settings_ini.setValue(i, "") # 初始化extract settings GenBankExtract_settings = QSettings( self.thisPath + '/settings/GenBankExtract_settings.ini', QSettings.IniFormat) GenBankExtract_settings.setFallbacksEnabled(False) dict_gbExtract_set = GenBankExtract_settings.value("set_version", None) if not dict_gbExtract_set: GenBankExtract_settings.setValue("set_version", init_sequence_type) else: # 如果没有预设的类型,就加上 flag = False for seq_type in init_sequence_type: if seq_type not in dict_gbExtract_set: flag = True dict_gbExtract_set[seq_type] = init_sequence_type[seq_type] if seq_type == "general" and "extract all features" not in dict_gbExtract_set[seq_type]: dict_gbExtract_set[seq_type]["extract all features"] = True if flag: GenBankExtract_settings.setValue("set_version", dict_gbExtract_set) # 初始化workflow设置 workflow_settings = QSettings( self.thisPath + '/settings/workflow_settings.ini', QSettings.IniFormat) workflow_settings.setFallbacksEnabled(False) workflow_settings.beginGroup('Workflow') keys = workflow_settings.allKeys() if not keys or ("Test run/PartitionFinder/comboBox" not in keys) or \ ("Align CDS, Refine alignment, Optimize alignment, " "Concatenation and Select best-fit model/Concatenate Sequence/checkBox" not in keys): for key in preset_workflow: workflow_settings.setValue(key, preset_workflow[key]) workflow_settings.endGroup() # 初始化sequence view settings Seq_viewer_setting = QSettings( self.thisPath + '/settings/Seq_viewer_setting.ini', QSettings.IniFormat) Seq_viewer_setting.setFallbacksEnabled(False) font = Seq_viewer_setting.value("display font", None) if not font: Seq_viewer_setting.setValue( "display font", QFont("Courier New", 11)) ini_fore_colors = {'W': '#000000', 'I': '#000000', 'U': '#ffffff', '-': '#000000', 'C': '#000000', 'A': '#000000', '.': '#000000', 'M': '#000000', 'V': '#000000', 'P': '#ffffff', 'K': '#000000', 'G': '#000000', 'N': '#000000', 'E': '#ffffff', '...': '#000000', 'H': '#000000', 'Q': '#ffffff', 'R': '#ffffff', 'D': '#000000', 'T': '#ffffff', 'L': '#000000', 'S': '#000000', 'F': '#000000', 'Y': '#000000'} dict_foreColor = Seq_viewer_setting.value("foreground colors", None) if not dict_foreColor: Seq_viewer_setting.setValue("foreground colors", ini_fore_colors) ini_back_colors = {'W': '#ff0000', 'I': '#ffcc00', 'U': '#ff0000', '-': '#ffffff', 'C': '#00aaff', 'A': '#99ff00', '.': '#ffffff', 'M': '#99ff00', 'V': '#ffcc00', 'P': '#3cb371', 'K': '#00ffff', 'G': '#ffcc00', 'N': '#87ceeb', 'E': '#ff00ff', '...': '#808080', 'H': '#ff0000', 'Q': '#ff00ff', 'R': '#ff69b4', 'D': '#87ceeb', 'T': '#ff0000', 'L': '#99ff00', 'S': '#ff0000', 'F': '#99ff00', 'Y': '#87ceeb'} dict_backColor = Seq_viewer_setting.value("background colors", None) if not dict_backColor: Seq_viewer_setting.setValue("background colors", ini_back_colors) # 初始化parse ANNT的settings init_value2 = {'tRNA Abbreviation': [['tRNA-Val', 'V'], ['tRNA-Tyr', 'Y'], ['tRNA-Trp', 'W'], ['tRNA-Thr', 'T'], ['tRNA-Ser', 'S'], ['tRNA-Pro', 'P'], ['tRNA-Phe', 'F'], ['tRNA-Met', 'M'], ['tRNA-Lys', 'K'], ['tRNA-Leu', 'L'], ['tRNA-Ile', 'I'], ['tRNA-His', 'H'], ['tRNA-Gly', 'G'], ['tRNA-Glu', 'E'], ['tRNA-Gln', 'Q'], ['tRNA-Cys', 'C'], ['tRNA-Asp', 'D'], ['tRNA-Asn', 'N'], ['tRNA-Arg', 'R'], ['tRNA-Ala', 'A']], 'Protein Gene Full Name': [['atp6', 'ATP synthase F0 subunit 6'], ['atp8', 'ATP synthase F0 subunit 8'], ['cox1', 'cytochrome c oxidase subunit 1'], ['cox2', 'cytochrome c oxidase subunit 2'], ['cox3', 'cytochrome c oxidase subunit 3'], ['cytb', 'cytochrome b'], ['nad1', 'NADH dehydrogenase subunit 1'], ['nad2', 'NADH dehydrogenase subunit 2'], ['nad3', 'NADH dehydrogenase subunit 3'], ['nad4', 'NADH dehydrogenase subunit 4'], ['nad4L', 'NADH dehydrogenase subunit 4L'], ['nad5', 'NADH dehydrogenase subunit 5'], ['nad6', 'NADH dehydrogenase subunit 6']], 'Name From Word': [['trnY(gta)', 'tRNA-Tyr(gta)'], ['trnY', 'tRNA-Tyr(gta)'], ['trnW(tca)', 'tRNA-Trp(tca)'], ['trnW', 'tRNA-Trp(tca)'], ['trnV(tac)', 'tRNA-Val(tac)'], ['trnV', 'tRNA-Val(tac)'], ['trnT(tgt)', 'tRNA-Thr(tgt)'], ['trnT', 'tRNA-Thr(tgt)'], ['trnR(tcg)', 'tRNA-Arg(tcg)'], ['trnR', 'tRNA-Arg(tcg)'], ['trnQ(ttg)', 'tRNA-Gln(ttg)'], ['trnQ', 'tRNA-Gln(ttg)'], ['trnP(tgg)', 'tRNA-Pro(tgg)'], ['trnP', 'tRNA-Pro(tgg)'], ['trnN(gtt)', 'tRNA-Asn(gtt)'], ['trnN', 'tRNA-Asn(gtt)'], ['trnM(cat)', 'tRNA-Met(cat)'], ['trnM', 'tRNA-Met(cat)'], ['trnK(ctt)', 'tRNA-Lys(ctt)'], ['trnK', 'tRNA-Lys(ctt)'], ['trnI(gat)', 'tRNA-Ile(gat)'], ['trnI', 'tRNA-Ile(gat)'], ['trnH(gtg)', 'tRNA-His(GTG)'], ['trnH', 'tRNA-His(gtg)'], ['trnG(tcc)', 'tRNA-Gly(tcc)'], ['trnG', 'tRNA-Gly(tcc)'], ['trnF(gaa)', 'tRNA-Phe(gaa)'], ['trnF', 'tRNA-Phe(gaa)'], ['trnE(ttc)', 'tRNA-Glu(ttc)'], ['trnE', 'tRNA-Glu(ttc)'], ['trnD(gtc)', 'tRNA-Asp(gtc)'], ['trnD', 'tRNA-Asp(gtc)'], ['trnC(gca)', 'tRNA-Cys(gca)'], ['trnC', 'tRNA-Cys(gca)'], ['trnA(tgc)', 'tRNA-Ala(tgc)'], ['trnA', 'tRNA-Ala(tgc)'], ['tRNA-Val', 'tRNA-Val(tac)'], ['tRNA-Tyr', 'tRNA-Tyr(gta)'], ['tRNA-Trp', 'tRNA-Trp(tca)'], [ 'tRNA-Thr', 'tRNA-Thr(tgt)'], ['tRNA-Pro', 'tRNA-Pro(tgg)'], ['tRNA-Phe', 'tRNA-Phe(gaa)'], ['tRNA-Met', 'tRNA-Met(cat)'], ['tRNA-Lys', 'tRNA-Lys(ctt)'], ['tRNA-Ile', 'tRNA-Ile(gat)'], ['tRNA-His', 'tRNA-His(GTG)'], ['tRNA-Gly', 'tRNA-Gly(tcc)'], ['tRNA-Glu', 'tRNA-Glu(ttc)'], ['tRNA-Gln', 'tRNA-Gln(ttg)'], ['tRNA-Cys', 'tRNA-Cys(gca)'], ['tRNA-Asp', 'tRNA-Asp(gtc)'], ['tRNA-Asn', 'tRNA-Asn(gtt)'], ['tRNA-Arg', 'tRNA-Arg(tcg)'], ['tRNA-Ala', 'tRNA-Ala(tgc)'], ['small subunit ribosomal RNA', '12S'], ['small ribosomal RNA subunit RNA', '12S'], ['small ribosomal RNA', '12S'], ['s-rRNA', '12S'], ['ribosomal RNA small subunit', '12S'], ['ribosomal RNA large subunit', '16S'], ['large subunit ribosomal RNA', '16S'], ['large ribosomal RNA subunit RNA', '16S'], ['large ribosomal RNA', '16S'], ['l-rRNA', '16S'], ['cytochrome c oxidase subunit III', 'COX3'], ['cytochrome c oxidase subunit II', 'COX2'], ['cytochrome c oxidase subunit I', 'COX1'], ['cytochrome c oxidase subunit 3', 'COX3'], ['cytochrome c oxidase subunit 2', 'COX2'], ['cytochrome c oxidase subunit 1', 'COX1'], ['cytochrome b', 'CYTB'], ['ND6', 'NAD6'], ['ND5', 'NAD5'], ['ND4L', 'NAD4L'], ['ND4', 'NAD4'], ['ND3', 'NAD3'], ['ND2', 'NAD2'], ['ND1', 'NAD1'], ['NADH dehydrogenase subunit5', 'NAD5'], ['NADH dehydrogenase subunit 6', 'NAD6'], ['NADH dehydrogenase subunit 5', 'NAD5'], ['NADH dehydrogenase subunit 4L', 'NAD4L'], ['NADH dehydrogenase subunit 4', 'NAD4'], ['NADH dehydrogenase subunit 3', 'NAD3'], ['NADH dehydrogenase subunit 2', 'NAD2'], ['NADH dehydrogenase subunit 1', 'NAD1'], ['CYT B', 'CYTB'], ['COXIII', 'COX3'], ['COXII', 'COX2'], ['COXI', 'COX1'], ['COIII', 'COX3'], ['COII', 'COX2'], ['COI', 'COX1'], ['COB', 'CYTB'], ['CO3', 'COX3'], ['CO2', 'COX2'], ['CO1', 'COX1'], ['ATPase subunit 6', 'ATP6'], ['ATPASE8', 'ATP8'], ['ATPASE6', 'ATP6'], ['ATPASE 8', 'ATP8'], ['ATPASE 6', 'ATP6'], ['ATP synthase F0 subunit 6', 'ATP6'], ['16s rRNA', '16S'], ['16S subunit RNA', '16S'], ['16S ribosomal RNA', '16S'], ['16S rRNA', '16S'], ['12s rRNA', '12S'], ['12S subunit RNA', '12S'], ['12S ribosomal RNA', '12S'], ['12S rRNA', '12S'], ['12S Ribosomal RNA', '12S']]} parseANNT_settings = QSettings( self.thisPath + '/settings/parseANNT_settings.ini', QSettings.IniFormat) parseANNT_settings.setFallbacksEnabled(False) dict_data = parseANNT_settings.value( "extract listed gene", None) if not dict_data: parseANNT_settings.setValue("extract listed gene", init_value2) # 更新1.2.1版本的时候,没有整理分类相关设置的 ini_data = [['Class', 'Order', 'Superfamily', 'Family', 'Subfamily', 'Genus'], [['', '*tera', '*dea', '*dae', '*nae', ''], ['', '', '*ida', '', '', ''], ['', '', '', '', '', ''], ['', '', '', '', '', ''], ['', '', '', '', '', '']]] data = self.settings_ini.value("Taxonomy Recognition", ini_data) if type(data) == list: # new_data = OrderedDict( [("Default taxonomy settings", data)]) self.settings_ini.setValue("Taxonomy Recognition", new_data) except: parent.exception_signal.emit(''.join( traceback.format_exception(*sys.exc_info())) + "\n") def display_check(self, array, gbManager, exceptSig, display_checkSig, progressBarSig): try: # print(int(QThread.currentThreadId())) # time_start = time.time() haveUpdate = gbManager.updateModifiedRecord(0, 50, progressBarSig) if haveUpdate: # print("haveUpdate") array = gbManager.fetch_array() # time_end = time.time() # print('totally cost1:', time_end - time_start) # time_start = time.time() haveNewArray = gbManager.updateArrayByInfo( 50, 50, progressBarSig) # 检查是否有需要补充的信息 if haveNewArray: # print("haveNewArray") array = gbManager.fetch_array() # time_end = time.time() # print('totally cost2:', time_end - time_start) # time_start = time.time() # validatedIDs = gbManager.fetchVerifiedIDs() #8秒 # time_end = time.time() # print('totally cost3:', time_end - time_start) # reverse_array = [array[0]] + sorted(array[1:], reverse=True) # 反转一下 # validatedIDs = gbManager.fetchVerifiedIDs(20, 80, progressBarSig) ##这一步也很慢 # displayTableModel.updateModel(array) # return display_checkSig.emit(array) except: exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 exceptSig.emit(exceptionInfo) # 激发这个信号 def normalizeMT_child(self, gbManager, dict_NML_settings): # 子线程要执行的任务 from src.handleGB import Normalize_MT parent = dict_NML_settings["parent"] try: validate_content = gbManager.fetchContentsByIDs( dict_NML_settings["list_IDs"]) dict_NML_settings["gbContents"] = validate_content parent.nmlgb = Normalize_MT(**dict_NML_settings) except: exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 print(exceptionInfo) parent.exception_signal.emit(exceptionInfo) # 激发这个信号 # return nmlgb def normalizeMT_main(self, nmlgb): from src.Lg_gbEditor import GbEditor # 子线程任务完成,主线程要执行的任务 gbeditor = GbEditor(nmlgb) # 添加最大化按钮 gbeditor.setWindowFlags( gbeditor.windowFlags() | Qt.WindowMinMaxButtonsHint) gbeditor.show() def checkUpdates(self, updateSig, exceptSig, mode="check", parent=None): try: with open(self.thisPath + os.sep + "NEWS.md", encoding="utf-8") as f: content = f.read() current_version = re.search( r"## *PhyloSuite v([^ ]+?) \(", content).group(1) if platform.system().lower() == "windows": version_url = "https://github.com/dongzhang0725/PhyloSuite/blob/master/NEWS.md" elif platform.system().lower() == "darwin": version_url = "https://github.com/dongzhang0725/PhyloSuite_Mac/blob/master/NEWS.md" elif platform.system().lower() == "linux": version_url = "https://github.com/dongzhang0725/PhyloSuite_linux/blob/master/NEWS.md" else: return def slot(urlContent): # 如果是error开始的字符串,这里写一下 if urlContent.startswith("Error"): if mode == "check": urlContent += "\nUpdate: please check network connection" exceptSig.emit(urlContent) return rgx = re.compile( r"(?sm)(<h2>.+?PhyloSuite v([^ ]+?) \(.+?</h2>.+?<ul>.+?</ul>)\s+?(?=<h2>|</article>)") rgx_search = rgx.search(urlContent) description = rgx_search.group(1) new_version = rgx_search.group(2) # print(current_version, new_version, description) updateSig.emit(current_version, new_version, description) httpread = HttpRead(parent=parent) httpread.finishedSig.connect(slot) httpread.doRequest(version_url) # httpread.doRequest() # print(urlContent) # 这里要花点儿时间,如果没网怎么办 # urlContent = text.read().decode('utf-8') except Exception as ex: updateSig.emit(None, None, None) # 关闭状态窗口 exceptionInfo = ''.join( traceback.format_exception( *sys.exc_info())) # 捕获报错内容,只能在这里捕获,没有报错的地方无法捕获 if mode == "check": if ex.__class__.__name__ == "URLError": exceptionInfo += "\nUpdate: please check network connection" exceptSig.emit(exceptionInfo) # 激发这个信号 def popupException(self, parent, exception): print(exception) msg = QMessageBox(parent) msg.setIcon(QMessageBox.Critical) if (platform.system().lower() == "linux") and ("Error creating SSL context" in exception): msg.setWindowTitle("Network error") msg.setText( "<p style='line-height:25px; height:25px'>Network library malfunction while checking for updates, " "see <a href=\"https://github.com/barryvdh/laravel-snappy/issues/217\">https://github.com/barryvdh/laravel-snappy/issues/217</a> for resolutions! " "Alternatively, you also can update PhyloSuite manually following this <a href=\"https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#7-1-Update-failed-how-to-revert-to-previous-settings-and-plugins\">instruction</a></p>") elif exception.endswith("Update: please check network connection"): msg.setWindowTitle("Update failed") msg.setText( "<p style='line-height:25px; height:25px'>Please check your network connection! " "Alternatively, you also can update PhyloSuite manually following this " "<a href=\"https://dongzhang0725.github.io/dongzhang0725.github.io/documentation/#7-1-Update-failed-how-to-revert-to-previous-settings-and-plugins\">instruction</a></p>") elif exception.endswith("GenBank file parse failed"): msg.setWindowTitle("GenBank file parse failed") msg.setText( "<p style='line-height:25px; height:25px'>GenBank file parse failed, please check your file carefully!</p>") elif exception.endswith("partial GenBank file failed"): exception = exception.replace("partial GenBank file failed", "") if re.search(r"(?sm)(LOCUS {7}(\S+).+?^//\s*?(?=LOCUS|$))", exception): list_IDs = [i[-1] for i in re.findall(r"(?sm)(LOCUS {7}(\S+).+?^//\s*?(?=LOCUS|$))", exception)] else: list_IDs=[] list_IDs = list_IDs[:20] if len(list_IDs) > 20 else list_IDs msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("GenBank file parse warning") msg.setText( "<p style='line-height:25px; height:25px'>Parsing of the following IDs failed: %s! Click \"Show Details\" to check them carefully.</p>"%", ".join(list_IDs)) elif exception.endswith("<normal exception>"): msg.setIcon(QMessageBox.Warning) msg.setWindowTitle("Warning") msg.setText( "<p style='line-height:25px; height:25px'>%s</p>"%exception.replace("<normal exception>", "")) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() return else: msg.setWindowTitle("Error") msg.setText( "<p style='line-height:25px; height:25px'>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 dongzhang0725@gmail.com</p>") msg.setDetailedText(exception) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def highlightWidgets(self, widget, *widgets, color="purple"): widgets_ = (widget,) + widgets for widget_ in widgets_: widget_.setGraphicsEffect( AnimationShadowEffect(QColor(color), widget_)) def deHighlightWidgets(self, widget, *widgets): widgets_ = (widget,) + widgets for widget_ in widgets_: graphicsEffect = widget_.graphicsEffect() try: graphicsEffect.stop() except: pass def openPath(self, path, parent): if platform.system().lower() == "windows": path = path.replace("/", "\\") os.startfile(path) elif platform.system().lower() == "darwin": if os.path.isfile(path): if os.system('open %s' % path) != 0: QMessageBox.information( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>Path: %s</p>" % path, QMessageBox.Ok) elif os.path.isdir(path): subprocess.Popen(["open", path]) elif platform.system().lower() == "linux": if os.path.isfile(path): if os.system('open %s' % path) != 0: QMessageBox.information( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>Path: %s</p>" % path, QMessageBox.Ok) elif os.path.isdir(path): subprocess.Popen(["xdg-open", path]) else: QMessageBox.information( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>Path: %s</p>" % path, QMessageBox.Ok) def checkPath(self, path, mode="normal", parent=None, allow_space=False): rgx = re.compile(r"([^a-zA-Z0-9\-\.\\\/\:_]+)", re.I) if not allow_space else re.compile(r"([^a-zA-Z0-9\-\.\\\/\:_ ]+)", re.I) if rgx.search(path): path_text = rgx.sub("<span style=\"color:red\">\\1</span>", path) path_text = path_text.replace("<span style=\"color:red\"> </span>", "<span style=\"background:red\"> </span>") illegalTXT = rgx.search(path).group() if re.search(r" +", illegalTXT): illegalTXT = "spaces" if mode == "silence": return illegalTXT, path_text elif mode != "app path": QMessageBox.critical( parent, "PhyloSuite", "<p style='line-height:25px; height:25px'>There may be some non-standard characters in the path (<span style=\"color:red\">%s</span>)," " try to use a different path. The archcriminal characters in the path are shown in red:<br> \"%s\"</p>" % ( illegalTXT, path_text), QMessageBox.Ok) return False else: mainwindow_settings = QSettings( self.thisPath + '/settings/mainwindow_settings.ini', QSettings.IniFormat, parent=parent) mainwindow_settings.setFallbacksEnabled(False) PopPathRemind = mainwindow_settings.value( "remind_path", "true") if self.str2bool(PopPathRemind): icon = ":/picture/resourses/icons8-cancel-50.png" message = "There may be some non-standard characters in the path of the PhyloSuite (<span style=\"color:red\">%s</span>), which can cause" \ " the software to <span style=\"color:red\">malfunction</span>. Try to reinstall the PhyloSuite to a different path " \ "(no spaces and no non-standard characters). The archcriminal characters in the path are shown in red:<br> \"%s\"" % ( illegalTXT, path_text) windInfo = NoteMessage( message, icon, singleBtn=True, parent=parent, hideReminder=True) windInfo.checkBox.clicked.connect( lambda bool_: mainwindow_settings.setValue("remind_path", not bool_)) if windInfo.exec_() == QDialog.Accepted: return return True # def fetch_thisPath(self): # thisPath = os.path.dirname(sys.argv[0]) # MAC打包后获取当前脚本路径方式 # thisPath = "." if not thisPath else thisPath ##有时候当前目录是空 # return thisPath def init_popen(self, commands): startupINFO = None if platform.system().lower() == "windows": startupINFO = subprocess.STARTUPINFO() startupINFO.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupINFO.wShowWindow = subprocess.SW_HIDE popen = subprocess.Popen( commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=startupINFO) else: popen = subprocess.Popen( commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=startupINFO, shell=True, preexec_fn=os.setsid) return popen def set_direct_dir(self, action, parent): name, ok = QInputDialog.getText( parent, 'Set output directory name', 'Output directory name:') if ok and name: name = self.refineName(name, remain_words="-") self.sync_dir(action, name=name) def sync_dir(self, action, name=None): #同步输出文件名字 if name: action.setText("Output Dir: %s"%name) else: time_str = time.strftime('%Y_%m_%d-%H_%M_%S', time.localtime(time.time())) action.setText("Output Dir: %s"%time_str) def fetchAllWorkFolders(self, currentPath, byRoot=False): if byRoot: rootPath = currentPath else: rootPath = currentPath.split("GenBank_File")[0] \ if "GenBank_File" in currentPath else currentPath.split("Other_File")[0] rootPath = rootPath.rstrip(r"/").rstrip(os.sep) list_wFolders = [] gb_path = rootPath + os.sep + "GenBank_File" for i in os.listdir(gb_path): if os.path.isdir(gb_path + os.sep + i) and i != "recycled": list_wFolders.append(gb_path + os.sep + i) other_path = rootPath + os.sep + "Other_File" for i in os.listdir(other_path): if os.path.isdir(other_path + os.sep + i) and i != "recycled": list_wFolders.append(other_path + os.sep + i) return list_wFolders def swithWorkPath(self, action, init=False, parent=None): allItems = self.fetchAllWorkFolders(parent.workPath) items = [] dict_items = {} index = 0 for num,i in enumerate(allItems): if os.path.samefile(i, parent.workPath): index = num if parent.parent.isWorkFolder(i, mode="gb"): item_text = "\"%s\" in GenBank_File" % os.path.basename(i) items.append(item_text) else: item_text = "\"%s\" in Other_File" % os.path.basename(i) items.append(item_text) dict_items[item_text] = i if not init: item, ok = QInputDialog.getItem(parent, "Set work path", "Work path:", items, index, False) if ok and item: ##初始化工作路径 action.setText("Work folder: %s"%item) parent.workPath = dict_items[item] action.setToolTip(parent.workPath) return action.setText("Work folder: %s"%items[index]) action.setToolTip(parent.workPath) def fetch_output_dir_name(self, action): #如果给flowchart命名了,那么就用那个名字来? return time.strftime('%Y_%m_%d-%H_%M_%S', time.localtime(time.time())) \ if re.search(r"\d+_\d+_\d+\-\d+_\d+_\d+", action.text()) or (action.text() == "Output Dir: ")\ else action.text().replace("Output Dir: ", "") def settingsGroup2Group(self, qsettingsPath, group1, group2, baseGroup=None): #将group1的设置复制到group2,如果group1是空,就用默认的那个。 qsettings = QSettings(qsettingsPath, QSettings.IniFormat) qsettings.setFallbacksEnabled(False) if baseGroup: #如果已经有一个group,就进入它 qsettings.beginGroup(baseGroup) #先清空group2 qsettings.beginGroup(group2) qsettings.remove("") qsettings.endGroup() #将group1的设置拷贝到字典 qsettings.beginGroup(group1) dict_settings = OrderedDict((i, qsettings.value(i, "")) for i in qsettings.allKeys()) qsettings.endGroup() #拷贝到group2 qsettings.beginGroup(group2) for j in dict_settings: qsettings.setValue(j, dict_settings[j]) qsettings.endGroup() if baseGroup: qsettings.endGroup() def getSTDOUT(self, popen): stdout = "" try: while True: try: out_line = popen.stdout.readline().decode("utf-8", errors="ignore") except UnicodeDecodeError: out_line = popen.stdout.readline().decode("gbk", errors="ignore") if out_line == "" and popen.poll() is not None: break stdout += out_line except: stdout = "" return stdout def getDefaultpluginPath(self, plugin=""): path = "" if plugin == "mafft": if platform.system().lower() == "windows": path = os.path.join(self.thisPath, "plugins", "mafft-win", "mafft.bat") elif platform.system().lower() == "darwin": path = os.path.join(self.thisPath, "plugins", "mafft-mac", "mafft.bat") elif plugin == "PF2": path = os.path.join(self.thisPath, "plugins", "partitionfinder-2.1.1") elif plugin == "gblocks": if platform.system().lower() == "windows": path = os.path.join(self.thisPath, "plugins", "Gblocks_0.91b", "Gblocks.exe") elif platform.system().lower() == "darwin": path = os.path.join(self.thisPath, "plugins", "Gblocks_0.91b", "Gblocks") elif plugin == "iq-tree": if platform.system().lower() == "windows": if platform.machine().endswith('64'): path = os.path.join(self.thisPath, "plugins", "iqtree-1.6.8-Windows", "bin", "iqtree.exe") else: path = os.path.join(self.thisPath, "plugins", "iqtree-1.6.8-Windows32", "bin", "iqtree.exe") elif platform.system().lower() == "darwin": path = os.path.join(self.thisPath, "plugins", "iqtree-1.6.8-MacOSX", "bin", "iqtree") elif plugin == "MrBayes": if platform.system().lower() == "windows": if platform.machine().endswith('64'): path = os.path.join(self.thisPath, "plugins", "MrBayes", "mrbayes_x64.exe") else: path = os.path.join(self.thisPath, "plugins", "MrBayes", "mrbayes_x86.exe") elif platform.system().lower() == "darwin": path = os.path.join(self.thisPath, "plugins", "MrBayes", "mb") elif plugin == "macse": path = os.path.join(self.thisPath, "plugins", "macse_v2.03.jar") elif plugin == "trimAl": if platform.system().lower() == "windows": path = os.path.join(self.thisPath, "plugins", "trimAl", "bin", "trimal.exe") return path def autoInputDisbled(self): if not hasattr(self, "mainwindow_settings"): self.mainwindow_settings = QSettings( self.thisPath + '/settings/mainwindow_settings.ini', QSettings.IniFormat, parent=self) # File only, no fallback to registry or or. self.mainwindow_settings.setFallbacksEnabled(False) return not self.str2bool(self.mainwindow_settings.value("auto detect", "true")) def init_judge(self, mode=None, filePath=None, parent=None): if parent.isOtherFileSelected(): ##展示的是otherfile的工作文件夹, 只导入选择的文件 autoInputs = self.judgeAutoInputs(mode, resultsPath=filePath) # 判断other file的文件 # 特殊情况的autoInputs modelfinder与MRBAYES:[list_alignments, None];IQ-TREE:[list_alignments, ["", None]] list_align, list_docx = parent.fetchSelectFile(filePath) if mode in ["ModelFinder", "IQ-TREE", "MrBayes"]: list_alignments = autoInputs[0] msa = list(set(list_alignments).intersection(set(list_align))) autoInputs = [msa, None] if mode != "IQ-TREE" else [msa, ["", None]] else: autoInputs = list(set(autoInputs).intersection(set(list_align))) elif parent.stackedWidget.currentIndex() != 8: #没有展示结果界面以及选中.data的时候 if mode in ["ModelFinder", "MrBayes"]: autoInputs = [None, None] elif mode == "IQ-TREE": autoInputs = [None, ["", None]] else: autoInputs = [] else: ##先找到可以用于自动导入的路径 resultsPath = parent.fetchResultsPath() if not resultsPath: ## 如果没有选中一个结果文件夹,自动获得最新的结果文件夹 if parent.isResultsFolder(filePath): subResults = self.fetchSubResults(filePath) if subResults: resultsPath = subResults[0] autoInputs = self.judgeAutoInputs(mode, resultsPath=resultsPath) return autoInputs def centerWindow(self, window): frameGm = window.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) window.move(frameGm.topLeft()) def emitCommands(self, logGuiSig, commands): logGuiSig.emit("%sCommands%s\n%s\n%s" % ("=" * 45, "=" * 45, commands, "=" * 98)) def getCurrentTaxSetData(self): ini_data = OrderedDict( [("Default taxonomy settings", [['Class', 'Order', 'Superfamily', 'Family', 'Subfamily', 'Genus'], [['', '*tera', '*dea', '*dae', '*nae', ''], ['', '', '*ida', '', '', ''], ['', '', '', '', '', ''], ['', '', '', '', '', ''], ['', '', '', '', '', '']]])]) currentTaxSetName = self.settings_ini.value("comboBox", "Default taxonomy settings") return self.settings_ini.value("Taxonomy Recognition", ini_data)[currentTaxSetName] class WorkThread(QThread): # workerfinished = pyqtSignal() def __init__(self, function, parent=None): super(WorkThread, self).__init__(parent) self.function = function # self.finished.connect(self.stopWork) # print("主线程", QThread.currentThreadId()) def run(self): self.function() def stopWork(self): if self.isRunning(): self.terminate() self.wait() class WorkRunThread(QRunnable): def __init__(self, function): super(WorkRunThread, self).__init__() self.function = function self.setAutoDelete(True) # self.finished.connect(self.stopWork) # print("主线程", QThread.currentThreadId()) def run(self): self.function() class SeqGrab(object): # 统计序列 def __init__(self, sequence, decimal=1): self.sequence = sequence.upper() self.length = len(self.sequence) self.size = str(self.length) self.decimal = "%." + "%df" % decimal self.A = self.sequence.count("A") self.T = self.sequence.count("T") self.C = self.sequence.count("C") self.G = self.sequence.count("G") self.A_percent = self.decimal % (self.A * 100 / self.length) self.T_percent = self.decimal % (self.T * 100 / self.length) self.C_percent = self.decimal % (self.C * 100 / self.length) self.G_percent = self.decimal % (self.G * 100 / self.length) self.AT_percent = self.decimal % ( float(self.A_percent) + float(self.T_percent)) self.GC_percent = self.decimal % ( float(self.C_percent) + float(self.G_percent)) self.GT_percent = self.decimal % ( float(self.G_percent) + float(self.T_percent)) if self.A != 0 or self.T != 0: # 避免不包含AT的序列 self.AT_skew = "%.3f" % ((self.A - self.T) / (self.A + self.T)) else: self.AT_skew = "NA" if self.G != 0 or self.C != 0: self.GC_skew = "%.3f" % ((self.G - self.C) / (self.G + self.C)) else: self.GC_skew = "NA" def splitCodon(self): first, second, third = "", "", "" for num, i in enumerate(self.sequence): if num % 3 == 0: first += i elif num % 3 == 1: second += i else: third += i return first, second, third class HttpWindowDownload(QDialog): errorSig = pyqtSignal(str) def __init__(self, parent=None, **dict_args): super(HttpWindowDownload, self).__init__(parent) self.parent = parent self.factory = Factory() self.url = QUrl(dict_args["httpURL"]) self.exportPath = dict_args["exportPath"] self.plugin_path = os.path.dirname(self.exportPath) self.outFile = QFile(self.exportPath) self.httpGetId = 0 self.httpRequestAborted = False self.download_button = dict_args["download_button"] self.tableWidget = dict_args["tableWidget"] self.row = dict_args["row"] self.status = dict_args["status"] self.qss_file = dict_args["qss"] self.installButtonSig = dict_args["installButtonSig"] self.flag = dict_args["flag"] self.exe_window = dict_args[ "exe_path_window"] if "exe_path_window" in dict_args else None self.save_pathSig = dict_args["save_pathSig"] self.dict_args = dict_args self.target_exe = dict_args["target"] self.qnam = QNetworkAccessManager() self.qnam.authenticationRequired.connect( self.slotAuthenticationRequired) self.qnam.sslErrors.connect(self.sslErrors) self.progressDialog = QProgressDialog(self.parent) self.progressDialog.resize(354, 145) self.progressDialog.canceled.connect(self.cancelDownload) self.progress_text = "" self.errorSig.connect(self.popupException) self.downloadFile() def startRequest(self, url): self.reply = self.qnam.get(QNetworkRequest(url)) self.reply.finished.connect(self.httpFinished) self.reply.readyRead.connect(self.httpReadyRead) self.reply.downloadProgress.connect(self.updateDataReadProgress) def downloadFile(self): fileName = os.path.splitext(os.path.basename(self.exportPath))[0] if QFile.exists(fileName): try: QFile.remove(fileName) except: pass if not self.outFile.open(QIODevice.WriteOnly): QMessageBox.information(self, "HTTP", "Unable to save the file %s: %s." % (fileName, self.outFile.errorString())) self.outFile = None return self.progressDialog.setWindowTitle("HTTP") self.top_text = " Downloading %s..." % fileName + " " * \ 5 if platform.system().lower( ) == "darwin" else "Downloading %s..." % fileName + " " * 10 self.progressDialog.setLabelText(self.top_text) self.httpRequestAborted = False self.progressDialog.setWindowFlags( self.progressDialog.windowFlags() | Qt.WindowMinMaxButtonsHint) self.startRequest(self.url) self.progressDialog.exec_() def cancelDownload(self): self.httpRequestAborted = True self.reply.abort() self.installButtonSig.emit( [self.download_button, self.tableWidget, self.row, "cancel", self.qss_file]) def httpFinished(self): if self.httpRequestAborted: if self.outFile is not None: self.outFile.close() self.outFile.remove() self.outFile = None self.reply.deleteLater() self.reply = None self.progressDialog.hide() return self.progressDialog.hide() self.outFile.flush() self.outFile.close() redirectionTarget = self.reply.attribute( QNetworkRequest.RedirectionTargetAttribute) if self.reply.error(): self.outFile.remove() QMessageBox.information(self, "HTTP", "Download failed: %s." % self.reply.errorString()) elif redirectionTarget is not None: newUrl = self.url.resolved(redirectionTarget) ret = QMessageBox.question(self, "HTTP", "Redirect to %s?" % newUrl.toString(), QMessageBox.Yes | QMessageBox.No) if ret == QMessageBox.Yes: self.url = newUrl self.reply.deleteLater() self.reply = None self.outFile.open(QIODevice.WriteOnly) self.outFile.resize(0) self.startRequest(self.url) return if self.flag == "RscriptPath": if platform.system().lower() == "windows": subprocess.call(self.exportPath) binary = "Rscript.exe" elif platform.system().lower() == "darwin": os.system("chmod 755 %s" % self.exportPath) os.system("open %s" % self.exportPath) binary = "Rscript" self.installButtonSig.emit( [self.download_button, self.tableWidget, self.row, "uninstall", self.qss_file]) #方便执行那个按钮 text = "Please specify the path of <span style='font-weight:600; color:#ff0000;'>'%s'</span> you installed!" % binary self.dict_args["installFinishedSig"].emit(text, "Rscript") elif self.flag == "python27": if platform.system().lower() == "windows": subprocess.call(self.exportPath) binary = "python.exe" elif platform.system().lower() == "darwin": os.system("chmod 755 %s" % self.exportPath) os.system("open %s" % self.exportPath) binary = "python" self.installButtonSig.emit( [self.download_button, self.tableWidget, self.row, "uninstall", self.qss_file]) # 方便执行那个按钮 text = "Please specify the path of <span style='font-weight:600; color:#ff0000;'>'%s'</span> you installed!</p>" % binary self.dict_args["installFinishedSig"].emit(text, "py27") else: self.parent.zipSig.emit("Unziping") self.zipWorker = WorkThread( lambda: self.factory.unZip(self.exportPath, self.target_exe, self.errorSig), parent=self) self.zipWorker.start() self.zipWorker.finished.connect(lambda : [self.parent.zipSig.emit("Unzip finished"), self.unzipFinished()]) # self.factory.unZip(self.exportPath, self.target_exe) # self.parent.zipSig.emit("Unzip finished") # self.unzipFinished() self.reply.deleteLater() self.reply = None self.outFile = None def httpReadyRead(self): if self.outFile is not None: self.outFile.write(self.reply.readAll()) def updateDataReadProgress(self, bytesRead, totalBytes): if self.httpRequestAborted: return progress_text = self.humanbytes( bytesRead) + "/" + self.humanbytes(totalBytes) if self.progress_text != progress_text: self.top_text = self.top_text.replace( self.progress_text, progress_text) if self.progress_text != "" else self.top_text + progress_text self.progressDialog.setLabelText(self.top_text) self.progress_text = progress_text oldValue = self.progressDialog.value() done_int = int(100 * bytesRead/totalBytes) if done_int > oldValue: self.progressDialog.setProperty("value", done_int) QCoreApplication.processEvents() def slotAuthenticationRequired(self, authenticator): import os from PyQt5 import uic ui = os.path.join(os.path.dirname(self.plugin_path) + os.sep + "uifiles", 'authenticationdialog.ui') dlg = uic.loadUi(ui) dlg.adjustSize() dlg.siteDescription.setText( "%s at %s" % (authenticator.realm(), self.url.host())) dlg.userEdit.setText(self.url.userName()) dlg.passwordEdit.setText(self.url.password()) if dlg.exec_() == QDialog.Accepted: authenticator.setUser(dlg.userEdit.text()) authenticator.setPassword(dlg.passwordEdit.text()) def sslErrors(self, reply, errors): errorString = ", ".join([str(error.errorString()) for error in errors]) ret = QMessageBox.warning(self, "HTTP", "One or more SSL errors has occurred: %s" % errorString, QMessageBox.Ignore | QMessageBox.Abort) if ret == QMessageBox.Ignore: self.reply.ignoreSslErrors() def humanbytes(self, B): 'Return the given bytes as a human friendly KB, MB, GB, or TB string' B = float(B) KB = float(1024) MB = float(KB ** 2) # 1,048,576 GB = float(KB ** 3) # 1,073,741,824 TB = float(KB ** 4) # 1,099,511,627,776 if B < KB: return '{0} {1}'.format(B, 'Bytes' if 0 == B > 1 else 'Byte') elif KB <= B < MB: return '{0:.2f} KB'.format(B / KB) elif MB <= B < GB: return '{0:.2f} MB'.format(B / MB) elif GB <= B < TB: return '{0:.2f} GB'.format(B / GB) elif TB <= B: return '{0:.2f} TB'.format(B / TB) def unzipFinished(self): path = self.plugin_path + os.sep + \ self.factory.topFolder if self.flag == "PF2" else self.plugin_path + \ os.sep + self.factory.exe_file if platform.system().lower() in ["darwin", "linux"]: # 给每个文件权限 if self.factory.topFolder: os.system("chmod -R 755 %s" % (self.plugin_path + os.sep + self.factory.topFolder)) if path: self.installButtonSig.emit( [self.download_button, self.tableWidget, self.row, self.status, self.qss_file]) self.save_pathSig.emit(self.flag, path) self.dict_args["installFinishedSig"].emit("installed successfully!", "other plugins") # QMessageBox.information( # self.parent, "Settings", "<p style='line-height:25px;height:25px'>installed successfully!</p>") else: self.installButtonSig.emit( [self.download_button, self.tableWidget, self.row, "uninstall", self.qss_file]) # QMessageBox.information( # self.parent, "Settings", "<p style='line-height:25px;height:25px'>installed failed!</p>") if QFile.exists(self.exportPath): # 安装完删除安装包 try: QFile.remove(self.exportPath) except: pass 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 dongzhang0725@gmail.com') msg.setWindowTitle("Error") msg.setDetailedText(exception) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() class HttpRead(QDialog): # 该方法不能放子线程里面,因为它本来就是异步的 finishedSig = pyqtSignal(str) def __init__(self, parent=None): super(HttpRead, self).__init__(parent) # self.doRequest() # request = QNetworkRequest() # request.setUrl(QUrl(self.url)) # manager = QNetworkAccessManager() # replyObject = manager.get(request) # print(replyObject) # replyObject.finished.connect(print) def doRequest(self, url): self.url = url req = QNetworkRequest(QUrl(self.url)) self.nam = QNetworkAccessManager() self.nam.finished.connect(self.handleResponse) self.nam.get(req) def handleResponse(self, reply): er = reply.error() if er == QNetworkReply.NoError: bytes_string = reply.readAll() # print(str(bytes_string, 'utf-8')) self.finishedSig.emit(str(bytes_string, 'utf-8')) else: self.finishedSig.emit("Error: " + reply.errorString()) # print("Error occured: ", er) # print(reply.errorString()) # QCoreApplication.quit() class QSingleApplication(QApplication): messageReceived = pyqtSignal(str) def __init__(self, *args, **kwargs): super(QSingleApplication, self).__init__(*args, **kwargs) appid = QApplication.applicationFilePath().lower().split("/")[-1] self._socketName = "qtsingleapp-" + appid # print("socketName", self._socketName) self._activationWindow = None self._activateOnMessage = False self._socketServer = None self._socketIn = None self._socketOut = None self._running = False # 先尝试连接 self._socketOut = QLocalSocket(self) self._socketOut.connectToServer(self._socketName) self._socketOut.error.connect(self.handleError) self._running = self._socketOut.waitForConnected() if not self._running: # 程序未运行 self._socketOut.close() del self._socketOut self._socketServer = QLocalServer(self) self._socketServer.listen(self._socketName) self._socketServer.newConnection.connect(self._onNewConnection) self.aboutToQuit.connect(self.removeServer) def handleError(self, message): print("handleError message: ", message) def isRunning(self): return self._running def activationWindow(self): return self._activationWindow def setActivationWindow(self, activationWindow, activateOnMessage=True): self._activationWindow = activationWindow self._activateOnMessage = activateOnMessage def activateWindow(self): if not self._activationWindow: return self._activationWindow.setWindowState( self._activationWindow.windowState() & ~Qt.WindowMinimized) self._activationWindow.raise_() self._activationWindow.activateWindow() def sendMessage(self, message, msecs=5000): if not self._socketOut: return False if not isinstance(message, bytes): message = str(message).encode() self._socketOut.write(message) if not self._socketOut.waitForBytesWritten(msecs): raise RuntimeError("Bytes not written within %ss" % (msecs / 1000.)) return True def _onNewConnection(self): if self._socketIn: self._socketIn.readyRead.disconnect(self._onReadyRead) self._socketIn = self._socketServer.nextPendingConnection() if not self._socketIn: return self._socketIn.readyRead.connect(self._onReadyRead) if self._activateOnMessage: self.activateWindow() def _onReadyRead(self): while 1: message = self._socketIn.readLine() if not message: break print("Message received: ", message) self.messageReceived.emit(message.data().decode()) def removeServer(self): self._socketServer.close() self._socketServer.removeServer(self._socketName) class Parsefmt(object): def __init__(self, error_message="", warning_message=""): self.error_message = error_message self.warning_message = warning_message self.factory = Factory() def judge(self, seq): # 要拿全部序列来判断,因为有可能会有很长的-;不过这个方法可以筛选是否为序列 set_standard = {'A', 'C', '-', 'T', 'G', 'N', '?'} set_RNA = {'A', 'C', '-', 'U', 'G', 'N', '?'} set_seq = set(seq.upper()) list_rest = list(set_seq - set_standard) list_rest_RNA = list(set_seq - set_RNA) count_rest = sum([seq.upper().count(i) for i in list_rest]) count_rest_RNA = sum([seq.upper().count(i) for i in list_rest_RNA]) if not len(seq): return "UNKNOWN" count_rest_ratio = count_rest / len(seq) count_rest_ratio_RNA = count_rest_RNA / len(seq) if count_rest_ratio < 0.03: return 'DNA' elif count_rest_ratio_RNA < 0.03: return 'RNA' else: return 'PROTEIN' def standardizeFas(self, handle, removeAlign=False): new_fas_contents = "" line = handle.readline() while line: while not line.startswith('>'): line = handle.readline() fas_name = line.strip().replace(">", "") fas_seq = "" line = handle.readline() while not line.startswith('>') and line != "": fas_seq += re.sub(r"\s", "", line) line = handle.readline() if removeAlign: fas_seq = fas_seq.replace("-", "") new_fas_contents += ">%s\n%s\n"%(self.factory.refineName(fas_name), fas_seq) return new_fas_contents # 生成self.dict_taxon def readfile(self, file, base=None, proportion=None, processSig=None): # 进度条相关 if processSig: processSig.emit(0) self.file = file self.dict_taxon = OrderedDict() self.rgxinitial = re.compile( r"(?smi)(^[\t ]*MATRIX[\r\n]|^ *(\d+) +(\d+)[\r\n]|^[\r\n])(.+?)(^[\r\n;])") self.rgxphy = re.compile(r"(\d+) +(\d+)") self.rgxnex = re.compile(r"(?i)ntax=\s*(\d+).+nchar=\s*(\d+)") with open(self.file, encoding="utf-8", errors='ignore') as file: content = file.read() # 判断是否裸数据 if content.count("\n") <= 1: # 裸数据只允许存在一行 self.dict_taxon[os.path.basename(self.file)] = re.sub( r" |\t|\n|\r", "", content) else: # 判定是否为nex格式 if self.rgxnex.search(content) and re.search( r"matrix\s*\n", content, re.I): taxnum, seqlenth = int( self.rgxnex.search(content).group(1)), int( self.rgxnex.search(content).group(2)) flag = "phynex" elif self.rgxphy.search(content) and (not re.search(r">.+[\r\n]", content)): taxnum, seqlenth = int( self.rgxphy.search(content).group(1)), int( self.rgxphy.search(content).group(2)) flag = "phynex" else: flag = "others" if flag == "phynex": stop = 0 content += "\n" # 因为有些交互式格式最后没有\n,所以最后一点儿无法匹配上 initial_match = self.rgxinitial.search( content, stop) # 详见【匹配上的东西.txt】 count = 1 # 用于处理是否第1次都匹配上hash1 fmt_flag = 0 # 如果是交互式,flag保持0,就不进行sequential的匹配 if initial_match is None: fmt_flag = 1 # 进度条相关 if processSig: total = len(self.rgxinitial.findall(content)) count_proc = 0 # 只要能匹配上就进行,如果下面的hash1也没匹配上,就不是交互式 while initial_match and fmt_flag != 1: item = initial_match.groups()[-2] # 取出序列部分 stop = initial_match.span()[1] - 1 # 如果不处理序列内部有空格的情况,这里的^符号可以去掉,那么就可以匹配名字前面有空白字符的情况 hash1 = re.findall( r"(?mi)(^[^\r\n\t\f\v #]+)[ \t]+([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", item) # 交互式特征匹配,名字+空白字符+序列 hash2 = re.findall( r"(?mi)^[ \t]+([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", item) # 前面没有名字的情况 hash1_beta = re.findall( r"(?mi)(^[^\r\n\t\f\v #]+)[ \t]+(( [GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)+) ?[\r\n]", item) # 这里新加了一个^以区别下面的空格起头 # 匹配交互式,每隔10个碱基一个空格的格式 hash2_beta = re.findall( r"(?mi)^[ \t]+(( [GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)+) ?[\r\n]", item) # 如果没匹配上,就是[],由于是第一次匹配上,可能会有nex的heading在里面 if hash1 != [] and count == 1: count += 1 # hash1匹配上了的标志 list_spes = [] # 存放序列长度 for spe, seq in hash1: list_spes.append(spe) self.dict_taxon[spe] = seq # seq要替换其他符号 if len(list_spes) != taxnum: # list_spes[0:-taxnum]索引非taxon for i in list_spes[0:-taxnum]: self.dict_taxon.pop(i) # 删除匹配到的heading的spe elif hash1 != [] and count == 2: # 第二个匹配位置,此时count应该很大了 for spe, seq in hash1: if spe in self.dict_taxon: self.dict_taxon[spe] += seq else: self.error_message += "Unexpected taxon: %s\n" % spe # 处理第二次是以空格开头的情况 elif hash1 == [] and hash2 != [] and count == 2: list_inter_keys = list(self.dict_taxon.keys()) for ords, seq in enumerate(hash2): self.dict_taxon[ list_inter_keys[ords]] += seq # 这里有一个顺序 if ords != taxnum - 1: self.error_message += "Unexpected taxon number: %d" % ( ords + 1) # 以下3个elif处理序列内部有空格的情况 elif hash1_beta != [] and count == 1: count += 1 list_spes = [] # 存放序列长度 # ('XtIFNi13', 'MSQ------S PIILPVVLLL LPVLVL---- CSPECPWLDN KGEFQVQKIL ', 'KGEFQVQKIL ') for spe, seq, last in hash1_beta: list_spes.append(spe) self.dict_taxon[spe] = seq.replace( " ", "") # seq要替换其他符号 if len(list_spes) != taxnum: for i in list_spes[0:-taxnum]: self.dict_taxon.pop(i) # 删除匹配到的heading的spe elif hash1_beta != [] and count == 2: for spe, seq, last in hash1_beta: if spe in self.dict_taxon: self.dict_taxon[spe] += seq.replace(" ", "") else: self.error_message += "Unexpected taxon: %s\n" % spe # 处理第二次是以空格开头的情况 elif hash1_beta == [] and hash2_beta != [] and count == 2: list_inter_keys = list(self.dict_taxon.keys()) for ords, seqs in enumerate(hash2_beta): # 这里seqs=('TVLDHMEPTE EIPDDCFLP- --LPPIDFTH N-MSLGAAAA IVDKVARETI ', 'IVDKVARETI ') self.dict_taxon[list_inter_keys[ords] ] += seqs[0].replace(" ", "") if ords != taxnum - 1: self.error_message += "Unexpected taxon number: %d" % ( ords + 1) else: # 匹配不上hash1和hash1_beta # print("匹配不上hash1和hash1_beta", fmt_flag) fmt_flag = 1 if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) initial_match = self.rgxinitial.search(content, stop) list_iterv_keys = list(self.dict_taxon.keys()) # interleave 判定是否正确 if list_iterv_keys != []: # 不等于[]就证明是交互式 for each in list_iterv_keys: if len(self.dict_taxon[each]) != seqlenth: self.error_message += "Unexpected sequence length in 【%s】, %d : %d\n!" % (each, len(self.dict_taxon[each]), seqlenth) # sequential if fmt_flag: # 证明不是交互式,进行sequential匹配 # 进度条相关 if processSig: total = content.count("\n") count_proc = 0 with open(self.file, encoding="utf-8", errors='ignore') as f1: line = f1.readline() if re.search(r"matrix\s*\n", content, re.I): # nex格式 while re.search( r"matrix\s*\n", line, re.I) is None: line = f1.readline() elif re.search(r"(\d+) +(\d+)", content): # phy和paml格式 while re.search( r"(\d+) +(\d+)", line, re.I) is None: line = f1.readline() line = f1.readline() messages = "" while line: while line and (line == "\n" or line == "\r\n"): # 跳过空行 line = f1.readline() if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) # 这是单行的sequential if re.search( r"([^\r\n\t\f\v ]+)[ \t]+([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", line, re.I): seqt_name = re.search( r"([^\r\n\t\f\v ]+)[ \t]+([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", line, re.I).group(1) seqt_seq = re.search( r"([^\r\n\t\f\v ]+)[ \t]+([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", line, re.I).group(2) if len(seqt_seq) == seqlenth: self.dict_taxon[seqt_name] = seqt_seq else: # print(seqt_seq) self.error_message += "Unexpected sequence length in 【%s】!, %d : %d\n!" % (seqt_name, len(seqt_seq), seqlenth) line = f1.readline() else: # 序列名下面有换行的sequential seqt_name = re.search( r"([^\r\n\f\v]+)(?=[\r\n])", line).group().replace( " ", "").replace( "\t", "") line = f1.readline() while line and (line == "\n" or line == "\r\n"): # 跳过空行 line = f1.readline() seqt_seq = line.strip().replace( " ", "").replace("\t", "") # 这里容易陷入死循环,如果差一点儿位置 while len(seqt_seq) != seqlenth and line: line = f1.readline() seqt_seq += line.strip().replace(" ", "").replace("\t", "") # 避免死循环,读到的长度大于文件显示长度就报错 if len(seqt_seq) > seqlenth: self.error_message += "Unexpected long sequence length in 【%s】!" % seqt_name break self.dict_taxon[seqt_name] = seqt_seq line = f1.readline() if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) if len(self.dict_taxon.keys()) == taxnum: break else: # 不是phy\nex\paml if re.search(r"^#.+[\r\n]", content): # MEGA格式 # 进度条相关 if processSig: total = content.count("\n") count_proc = 0 with open(self.file, encoding="utf-8", errors='ignore') as f2: line = f2.readline() while line: while (not line.startswith('#') or re.search( r"^#mega", line, re.I)) and line: line = f2.readline() mega_name = line.strip().replace("#", "") mega_seq = "" line = f2.readline() while not line.startswith('#') and line: mega_seq += line.strip().replace(" ", "").replace("\t", "") line = f2.readline() if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) self.dict_taxon[mega_name] = mega_seq if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) elif re.search(r"^>.+[\r\n]", content): # fas格式 # 进度条相关 if processSig: total = content.count("\n") count_proc = 0 with open(self.file, encoding="utf-8", errors='ignore') as f2: line = f2.readline() while line: while (not line.startswith('>')) and line: line = f2.readline() fas_name = line.strip().replace(">", "") fas_seq = "" line = f2.readline() while not line.startswith('>') and line: fas_seq += line.strip().replace(" ", "").replace("\t", "") line = f2.readline() if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) self.dict_taxon[fas_name] = fas_seq if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) else: # 匹配aln格式 rgxaln = re.compile( r"([^\r\n\t\f\v #]+)[ \t]{4,}([GAVLIFWYDHNEKQMRSTCPUBX\-\?]+)[\r\n]", re.I) # 与hash1一样 stop = 0 match = rgxaln.search(content, stop) # 进度条相关 if processSig: total = len(self.rgxinitial.findall(content)) count_proc = 0 while match: name, sequence, stop = match.group(1), match.group( 2), match.span()[1] - 1 # 这里span-1是\n,span是下一行开始的名字 # 替换序列内部的非序列成分 sequence = re.sub(r"\r|\n|\t| ", "", sequence) if name in self.dict_taxon: self.dict_taxon[name] += sequence else: self.dict_taxon[name] = sequence # print(name,stop) # #如果陷入死循环,开启这个方法,可以查看是哪个序列含有非法符号,把对应序列复制出来,用(?i)[^ATCG-]这个正则查找 if processSig: count_proc += 1 processSig.emit( base + (count_proc / total) * proportion) match = rgxaln.search(content, stop) def standard_name(): self.standard_dict_taxon = OrderedDict() factory = Factory() for i in self.dict_taxon: new_name = factory.refineName(i) # 去掉头尾空格,并替换空格 self.standard_dict_taxon[new_name] = self.dict_taxon[i] standard_name() self.content = content return self.standard_dict_taxon # 判断pattern,以dict_taxon形式传入参数 def which_pattern(self, dict_taxon, file): list_pattern = [] # 每一个taxon的序列都验证一些类型存放到列表里面 [name, AA] for name, seq in dict_taxon.items(): if len(seq) != seq.count("-"): # 过滤掉全是-的序列,不判断 list_pattern.append([name, self.judge(seq)]) list_seq_types = [pattern[1] for pattern in list_pattern] set_pattern = set(list_seq_types) # 将其转换为集合 # 长度不等于一就证明有taxon跟其他taxon类型不同 if set_pattern and len(set_pattern) != 1: type_details = "Sequence types:\n" + "\n".join([":\t".join(i) for i in list_pattern]) + "\n" self.warning_message += "Warning: mixed nucleotide and AA sequences in %s!\n%s" % (file, type_details) pattern_counter = Counter(list_seq_types) most_common_pattern = pattern_counter.most_common(1)[0][0] return most_common_pattern elif not list_seq_types: return "N/A" else: return list_seq_types[0] class Convertfmt(object): exception_signal = pyqtSignal(str) def __init__(self, **kwargs): self.dict_args = kwargs for i in ["export_phylip", "export_nex", "export_nexi", "export_paml", "export_axt", "export_fas", "export_stat"]: if i not in self.dict_args: self.dict_args[i] = False self.error_message = "" self.parsefmt = Parsefmt(self.error_message) self.factory = Factory() self.unaligns = [] self.f3 = None def exec_(self): if "files" in self.dict_args: self.outpath = self.dict_args["export_path"] # 如果用户没有传文件,就是调用generate_each导入dict_taxon total = len(self.dict_args["files"]) for num, file in enumerate(self.dict_args["files"]): dict_taxon = self.parsefmt.readfile(file) if ("remove B" in self.dict_args) and self.dict_args["remove B"]: for taxon in dict_taxon: dict_taxon[taxon] = re.sub(r"B", "-", dict_taxon[taxon], re.I) self.error_message += self.parsefmt.error_message if self.factory.is_aligned(dict_taxon): self.generate_each(dict_taxon, file) else: self.unaligns.append(os.path.basename(file)) if "progressSig" in self.dict_args: self.dict_args["progressSig"].emit( ((num + 1) * 100 / total)) # if self.error_message and "exception_signal" in self.dict_args: # self.dict_args["exception_signal"].emit(self.error_message) # if self.unaligns: # self.dict_args["unaligned_signal"].emit(self.unaligns) def generate_each(self, dict_taxon, file): # 判定序列是否比对过 self.phy = '' self.nex = '' self.nxs_inter = '' self.fas = '' self.paml = '' self.axt_name = '' self.axt_seq = '' self.raw_data = "" self.statistics = 'Name,Length\n' self.count = 1 self.dict_taxon = dict_taxon if self.factory.is_aligned(self.dict_taxon): # 判断是是否比对过 list_lenth = [len(i) for i in self.dict_taxon.keys()] self.longest = max(list_lenth) for self.name, self.seq in sorted(self.dict_taxon.items()): self.seq = self.seq.upper() self.name = self.name.ljust(self.longest) # 统计名字长度,让序列名对齐 self.assign() self.complete(file) if "userSave" in self.dict_args: self.userSave(self.dict_args["userSave"]) else: self.save(file) else: self.exception_signal.emit("Unaligned!") def align(self): list_seq = re.findall(r'(.{60})', self.seq) remainder = len(self.seq) % 60 if remainder == 0: self.align_seq = '\n'.join(list_seq) + '\n' else: self.align_seq = '\n'.join( list_seq) + '\n' + self.seq[-remainder:] + '\n' def assign(self): self.align() self.statistics += self.name.strip() + ',' + str(len(self.seq)) + '\n' self.fas += ">" + \ self.name.strip() + "\n" + self.seq + "\n" self.phy += self.name + ' ' + self.seq + '\n' self.nex += self.name + ' ' + self.seq + '\n' self.paml += self.name.strip() + '\n' + \ self.align_seq + '\n' #.replace("T", "U") + '\n' self.axt_name += self.name.strip() + '-' self.axt_seq += self.seq + '\n' #.replace("T", "U") + '\n' self.raw_data += self.seq + "\n" self.count += 1 def nxs_interfmt(self): length = len(self.dict_taxon[self.list_fmtkeys[-1]]) # 总长 integer = length // 60 num = 1 while num <= integer: for i in self.list_fmtkeys: # 对齐名字 self.nxs_inter += i.ljust(self.longest) + ' ' + self.dict_taxon[i][ (num - 1) * 60:num * 60] + '\n' self.nxs_inter += "\n" num += 1 if length % 60 != 0: for i in self.list_fmtkeys: self.nxs_inter += i.ljust(self.longest) + ' ' + self.dict_taxon[i][ (num - 1) * 60:length] + '\n' self.nxs_inter += ';\nEND;\n' def complete(self, file): self.pattern = self.parsefmt.which_pattern( self.dict_taxon, file) self.phy = ' ' + str(self.count - 1) + ' ' + \ str(len(self.seq)) + '\n' + self.phy self.nex = '#NEXUS\nBEGIN DATA;\ndimensions ntax=%s nchar=%s;\nformat missing=?\ndatatype=%s gap= -;\n\nmatrix\n'\ % (str(self.count - 1), str(len(self.seq)), self.pattern) + self.nex + ';\nEND;\n' self.nxs_inter = '#NEXUS\nBEGIN DATA;\ndimensions ntax=%s nchar=%s;\nformat missing=?\ndatatype=%s gap= - interleave;\n\nmatrix\n'\ % (str(self.count - 1), str(len(self.seq)), self.pattern) self.paml = str(self.count - 1) + ' ' + \ str(len(self.seq)) + '\n\n' + self.paml self.axt = self.axt_name.strip('-') + '\n' + self.axt_seq self.list_fmtkeys = sorted(list(self.dict_taxon.keys())) self.nxs_interfmt() # 生成interleave格式的nxs def save(self, inputfile): rawname = os.path.splitext(os.path.basename(inputfile))[0] if self.dict_args["export_phylip"]: with open(self.outpath + os.sep + rawname + '.phy', 'w', encoding="utf-8") as f1: f1.write(self.phy) if self.dict_args["export_nex"]: self.f2 = self.outpath + os.sep + rawname + '.nex' # 由于贝叶斯需要 with open(self.f2, 'w', encoding="utf-8") as f2: f2.write(self.nex) if self.dict_args["export_nexi"]: self.f3 = self.outpath + os.sep + rawname + '_interleave.nex' with open(self.f3, 'w', encoding="utf-8") as f3: f3.write(self.nxs_inter) if self.dict_args["export_paml"]: with open(self.outpath + os.sep + rawname + '.PML', 'w', encoding="utf-8") as f4: f4.write(self.paml) if self.dict_args["export_axt"]: with open(self.outpath + os.sep + rawname + '.axt', 'w', encoding="utf-8") as f5: f5.write(self.axt) if self.dict_args["export_fas"]: self.f6 = self.outpath + os.sep + rawname + '.fas' with open(self.f6, 'w', encoding="utf-8") as f6: f6.write(self.fas) if self.dict_args["export_stat"]: with open(self.outpath + os.sep + rawname + '_stat.csv', 'w', encoding="utf-8") as f7: f7.write(self.statistics) def userSave(self, outputName): if self.dict_args["export_phylip"]: with open(outputName, 'w', encoding="utf-8") as f1: f1.write(self.phy) if self.dict_args["export_nex"]: with open(outputName, 'w', encoding="utf-8") as f2: f2.write(self.nex) if self.dict_args["export_nexi"]: with open(outputName, 'w', encoding="utf-8") as f3: f3.write(self.nxs_inter) if self.dict_args["export_paml"]: with open(outputName, 'w', encoding="utf-8") as f4: f4.write(self.paml) if self.dict_args["export_axt"]: with open(outputName, 'w', encoding="utf-8") as f5: f5.write(self.axt) if self.dict_args["export_fas"]: with open(outputName, 'w', encoding="utf-8") as f6: f6.write(self.fas) if self.dict_args["export_stat"]: with open(outputName, 'w', encoding="utf-8") as f7: f7.write(self.statistics) class Find(QDialog): def __init__(self, parent=None, target=None, sig=None): # QDialog.__init__(self, parent) super(Find, self).__init__(parent) self.parent = parent self.lastMatch = None self.target = target self.findSig = sig # 如果传入了要匹配的目标,就直接执行find if self.target: self.find() else: self.initUI() def initUI(self): # Button to search the document for something findButton = QPushButton("Find", self) findButton.clicked.connect(self.find) # Button to replace the last finding replaceButton = QPushButton("Replace", self) replaceButton.clicked.connect(self.replace) # Button to remove all findings allButton = QPushButton("Replace all", self) allButton.clicked.connect(self.replaceAll) # Normal mode - radio button self.normalRadio = QRadioButton("Normal", self) self.normalRadio.toggled.connect(self.normalMode) # Regular Expression Mode - radio button self.regexRadio = QRadioButton("RegEx", self) self.regexRadio.toggled.connect(self.regexMode) # The field into which to type the query self.findField = QTextEdit(self) self.findField.resize(250, 50) # The field into which to type the text to replace the # queried text self.replaceField = QTextEdit(self) self.replaceField.resize(250, 50) optionsLabel = QLabel("Options: ", self) # Case Sensitivity option self.caseSens = QCheckBox("Case sensitive", self) # Whole Words option self.wholeWords = QCheckBox("Whole words", self) # Layout the objects on the screen layout = QGridLayout(self) layout.addWidget(self.findField, 1, 0, 1, 4) layout.addWidget(self.normalRadio, 2, 2) layout.addWidget(self.regexRadio, 2, 3) layout.addWidget(findButton, 2, 0, 1, 2) layout.addWidget(self.replaceField, 3, 0, 1, 4) layout.addWidget(replaceButton, 4, 0, 1, 2) layout.addWidget(allButton, 4, 2, 1, 2) # Add some spacing spacer = QWidget(self) spacer.setFixedSize(0, 10) layout.addWidget(spacer, 5, 0) layout.addWidget(optionsLabel, 6, 0) layout.addWidget(self.caseSens, 6, 1) layout.addWidget(self.wholeWords, 6, 2) self.setGeometry(300, 300, 360, 250) self.setWindowTitle("Find and Replace") self.setLayout(layout) # By default the normal mode is activated self.normalRadio.setChecked(True) # rect = self.parent.geometry() # x = rect.x() + rect.width() / 2 - self.width() / 2 # y = rect.y() + rect.height() / 2 - self.height() # self.move(x, y) def find(self): # Grab the parent's text text = self.parent.toPlainText() # And the text to find query = self.findField.toPlainText( ) if self.target is None else self.target # If the 'Whole Words' checkbox is checked, we need to append # and prepend a non-alphanumeric character if self.target is None: if self.wholeWords.isChecked(): query = r'\W' + query + r'\W' # By default regexes are case sensitive but usually a search isn't # case sensitive by default, so we need to switch this around here if self.target is None: flags = 0 if self.caseSens.isChecked() else re.I else: flags = 0 for i in "[]*.?+$^(){}|\/": query = query.replace(i, "[" + i + "]") # Compile the pattern pattern = re.compile(query, flags) # If the last match was successful, start at position after the last # match's start, else at 0 start = self.lastMatch.start() + 1 if self.lastMatch else 0 # The actual search self.lastMatch = pattern.search(text, start) if self.lastMatch: start = self.lastMatch.start() end = self.lastMatch.end() # If 'Whole words' is checked, the selection would include the two # non-alphanumeric characters we included in the search, which need # to be removed before marking them. if self.target is None: if self.wholeWords.isChecked(): start += 1 end -= 1 self.moveCursor(start, end) else: if self.findSig is None: # We set the cursor to the end if the search was unsuccessful self.parent.moveCursor(QTextCursor.End) else: self.findSig.emit() def replace(self): # Grab the text cursor cursor = self.parent.textCursor() # Security if self.lastMatch and cursor.hasSelection(): # We insert the new text, which will override the selected # text cursor.insertText(self.replaceField.toPlainText()) # And set the new cursor self.parent.setTextCursor(cursor) def replaceAll(self): # Set lastMatch to None so that the search # starts from the beginning of the document self.lastMatch = None # Initial find() call so that lastMatch is # potentially not None anymore self.find() # Replace and find until find is None again while self.lastMatch: self.replace() self.find() def regexMode(self): # First uncheck the checkboxes self.caseSens.setChecked(False) self.wholeWords.setChecked(False) # Then disable them (gray them out) self.caseSens.setEnabled(False) self.wholeWords.setEnabled(False) def normalMode(self): # Enable checkboxes (un-gray them) self.caseSens.setEnabled(True) self.wholeWords.setEnabled(True) def moveCursor(self, start, end): # We retrieve the QTextCursor object from the parent's QTextEdit cursor = self.parent.textCursor() # 先把目标位置顶到中间,对waring效果不好 # cursor.setPosition(start+1000) # self.parent.setTextCursor(cursor) # Then we set the position to the beginning of the last match cursor.setPosition(start) # Next we move the Cursor by over the match and pass the KeepAnchor parameter # which will make the cursor select the the match's text cursor.movePosition( QTextCursor.Right, QTextCursor.KeepAnchor, end - start) # And finally we set this new cursor as the parent's self.parent.setTextCursor(cursor)