class ExtendedLineEdit(QLineEdit): signal_send_movie = pyqtSignal(list) signal_search_movie = pyqtSignal(str) signal_request_movie_data = pyqtSignal(int) signal_set_loading = pyqtSignal(str, bool) DEBUG = False def __init__(self, parent=None): super(ExtendedLineEdit, self).__init__(parent) self.movies = [] self.completer_lw = QListWidget() self.model = self.completer_lw.model() self.completer = QCompleter(self.model, self) self.completer.setPopup(self.completer_lw) self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.completer.setCompletionRole( Qt.UserRole ) # Change role because default EditRole is paited to list somehow self.setCompleter(self.completer) self.textEdited.connect(self.text_edited) #self.completer.activated[QModelIndex].connect(self.on_completer_activated) #self.completer.activated[str].connect(self.on_completer_activated_str) self.completer_lw.itemClicked.connect(self.item_clicked) self.installEventFilter(self) def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonPress: if event.button() == Qt.LeftButton: self.completer.complete() return super(ExtendedLineEdit, self).eventFilter(obj, event) def setText(self, text: str) -> None: if self.DEBUG: print('setText', text, self.sender()) super(ExtendedLineEdit, self).setText(text) if not isinstance(self.sender(), QCompleter) and text: self.text_edited(text) @pyqtSlot(list) def update_movies_list(self, result): if self.DEBUG: print('update_movies_list', result[0], len(result) - 1) type = result.pop(0) if type == 'db': self.movies = [x + [ type, ] for x in result] self.completer_lw.clear() self.completer.complete() else: for item in result: self.movies.append(item + [ type, ]) for item in result: cwidget = MyWidget() cwidget.label_movie_name.setText(item[1] if item[1] else item[2]) cwidget.label_original_name.setText(item[2]) cwidget.label_source.setText(type) cwidget.label_year.setText(str(item[3])) completer_myQListWidgetItem = QListWidgetItem(self.completer_lw) completer_myQListWidgetItem.setSizeHint(cwidget.sizeHint()) completer_myQListWidgetItem.setData(Qt.UserRole, item[1]) self.completer_lw.addItem(completer_myQListWidgetItem) self.completer_lw.setItemWidget(completer_myQListWidgetItem, cwidget) if self.hasFocus() and not self.completer_lw.isVisible() and len( self.movies) > 0: self.completer.complete() @pyqtSlot(str) def text_edited(self, text): if self.DEBUG: print('text_edited', text, self.sender()) if text and isinstance(self.sender(), ExtendedLineEdit): self.signal_search_movie.emit(text) @pyqtSlot(str) def on_completer_activated_str(self, name: str): if self.DEBUG: print('on_completer_activated_str', name) @pyqtSlot(QModelIndex) def on_completer_activated(self, index: QModelIndex): if self.DEBUG: print('on_completer_activated', index.row()) item = self.movies[index.row()] if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) @pyqtSlot(QListWidgetItem) def item_clicked(self, item: QListWidgetItem): if self.DEBUG: print('item_clicked', item, [i[1] for i in self.movies]) index = self.completer_lw.indexFromItem(item) item = self.movies[index.row()] type = item[-1] if type == 'db': self.signal_send_movie.emit(item) else: self.signal_set_loading.emit('movie', True) if len(item) > 13: self.signal_send_movie.emit(item[:13]) self.signal_request_movie_data.emit(item[13]) else: self.signal_send_movie.emit(item) def reset(self): if self.DEBUG: print('reset') self.completer_lw.clear() self.clearFocus()
class FileRenameWidget(QWidget): """ 给文件加后缀例子:(^[^\.]+) 替换成\1_26 """ class ReplaceTemplateWidget(QWidget): def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.fileDir = "" self.fileDragList = [] self.fileList = [] self.filterRe = "" self.dirDeepSearch = False self.filterUseRe = False self.replaceUseRe = False self.fileDirLineEdit = FileOpenDirLineEdit() self.dirDeepSearchCheckBox = QCheckBox("递归搜素目录") self.filterReLineEdit = QLineEdit(minimumWidth=240) self.filterUseReCheckBox = QCheckBox("使用re") self.replaceTmpBtn = QPushButton("模板")#"<span style='color:blue'>模板</span>" self.replaceOldLineEdit = QLineEdit(minimumWidth=240) self.replaceNewLineEdit = QLineEdit(minimumWidth=240) self.replaceUseReCheckBox = QCheckBox("使用re") self.execButton = QPushButton("execute(执行)", maximumWidth=100) self.replacePromptLabel = QLabel() self.fileListText = DragLineTextEdit() gridLayout = GridLayout(self, contentsMargins=(10, 10, 10, 10), spacing=10) replaceLayout = HBoxLayout(contentsMargins=(0, 10, 10, 10), spacing=10, widgets=[self.replaceOldLineEdit, QLabel("替换成"), self.replaceNewLineEdit, self.replaceUseReCheckBox, self.replaceTmpBtn,LayStretch(1)]) glWidgets = [ [GLItem(QLabel("目录:"), 1, 2), GLItem(self.fileDirLineEdit, 1, 4), GLItem(self.dirDeepSearchCheckBox, 1, 2),GLItem(QWidget(), 1, -1)], [GLItem(QLabel("过滤字符串:"), 1, 2), GLItem(self.filterReLineEdit, 1, 3), GLItem(self.filterUseReCheckBox, 1, 2),GLItem(QWidget(), 1, 3)], [GLItem(QLabel("替换:"), 1, 2),GLItem(replaceLayout, 1, -1)], [GLItem(self.execButton, 1, 2), GLItem(self.replacePromptLabel, 1, -1)], [GLItem(self.fileListText, 1, -1)] ] gridLayout.addWidgets(glWidgets) self.replaceTmpWidget = QListWidget(self) self.replaceTmpList = [{"text":"添加后缀","name":"add_suffix"}] self.replaceTmpWidget.addItems(replaceTmp['text'] for replaceTmp in self.replaceTmpList) self.replaceTmpWidget.resize(100, 300) self.replaceTmpWidget.hide() # 注册事件 self.registerSignalConnect() def registerSignalConnect(self): self.fileDirLineEdit.textChanged.connect(self.fileDirChanged) # self.filterReLineEdit.textChanged.connect(self.filterReChanged) # 回车触发事件(只输入\,re.compile会报错) self.filterReLineEdit.returnPressed.connect(self.filterReConfirm) self.replaceNewLineEdit.returnPressed.connect(lambda : self._setAllFileList()) self.fileListText.dragSignal.connect(self.fileDrag) """ 复选框的三种状态: Qt.Checked:2, 组件没有被选中(默认) Qt.PartiallyChecked1:, 组件被半选中 Qt.Unchecked:0, 组件被选中 """ #当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行。(我测试过确实是,网上有些人说的不对) #self.filterUseReCheckBox.stateChanged.connect(lambda state: print("111111111")) #self.filterUseReCheckBox.stateChanged.connect(lambda state: print("222222222")) self.dirDeepSearchCheckBox.stateChanged.connect(lambda state: setattr(self, "dirDeepSearch", state > 0)) self.dirDeepSearchCheckBox.stateChanged.connect(lambda state: self._setFileList()) self.filterUseReCheckBox.stateChanged.connect(lambda state: setattr(self, "filterUseRe", state>0) ) self.filterUseReCheckBox.stateChanged.connect(lambda state: self._setFileList()) self.replaceUseReCheckBox.stateChanged.connect(lambda state: setattr(self, "replaceUseRe", state>0) ) self.replaceUseReCheckBox.stateChanged.connect(lambda state: self._setFileList()) self.execButton.clicked.connect(self.execButtonClicked) self.replaceTmpBtn.clicked.connect(self.replaceTmpBtnClicked) self.replaceTmpWidget.itemClicked.connect(self.replaceTmpClicked) def replaceTmpBtnClicked(self,*param): if self.replaceTmpWidget.isVisible(): self.replaceTmpWidget.hide() else: #获取纵坐标,obj.y(),包括框架。如果没有父控件是相对于桌面坐标。 self.replaceTmpWidget.move(self.width()-100, self.replaceTmpBtn.y()+ self.replaceTmpBtn.height()) self.replaceTmpWidget.show() self.replaceTmpWidget.raise_() def replaceTmpClicked(self,item): replaceTmp = self.replaceTmpList[self.replaceTmpWidget.currentRow()] if replaceTmp["name"] == "add_suffix": self.replaceOldLineEdit.setText(r"(^[^\.]+)") self.replaceNewLineEdit.setText(r"\1_26") self.replaceUseReCheckBox.setChecked(True) self.replaceTmpWidget.hide() def fileDrag(self, links): for link in links: #python集合的+=要求右边是是可迭代对象 #self.fileDragList += link self.fileDragList.append(link) self._setAllFileList() def _getReplacePrompt(self, fileList): if len(fileList) == 0: self.replacePromptLabel.setText("") else: _,fileName = os.path.split(fileList[0]) fmt = "<span style='color:#02AC03;font-size:18px;'>{}</span> 修改成:<span style='color:#02AC03;font-size:18px;'>{}</span>" if not self.replaceUseRe: self.replacePromptLabel.setText(fmt.format(fileName, fileName.replace(self.replaceOldLineEdit.text(), self.replaceNewLineEdit.text()))) else: self.replacePromptLabel.setText(fmt.format(fileName, re.sub(self.replaceOldLineEdit.text(), self.replaceNewLineEdit.text(), fileName) )) def _getAllFileList(self): if len(self.fileDragList) == 0: return self.fileList fileSet = set(self.fileList) fileList = [file for file in self.fileList] for file in self.fileDragList: if file in fileSet: continue fileList.append(file) fileSet.add(file) return fileList def _setAllFileList(self): fileList = self._getAllFileList() self._getReplacePrompt(fileList) self.fileListText.setPlainText("\n".join(fileList)) def execButtonClicked(self, *params): reply = QMessageBox.information(self, '提示', '确定要修改文件名吗?', QMessageBox.Ok | QMessageBox.No, QMessageBox.No) if reply != QMessageBox.Ok: return old = self.replaceOldLineEdit.text() new = self.replaceNewLineEdit.text() for dirname, filename in (os.path.split(path) for path in self._getAllFileList()): if not self.replaceUseRe: newname = filename.replace(old, new) else: newname = re.sub(old, new, filename) if filename != newname: os.rename(os.path.join(dirname, filename), os.path.join(dirname, newname)) self.fileDragList.clear() self._setFileList() def fileDirChanged(self, fileDir): self.fileDir = fileDir self._setFileList() def filterReChanged(self, filterRe): self.filterRe = filterRe self._setFileList() def filterReConfirm(self): self.filterRe = self.filterReLineEdit.text() self._setFileList() def _setFileList(self): self.fileList.clear() if not os.path.isdir(self.fileDir): return try: if self.filterUseRe: filterRe = re.compile(self.filterRe) except Exception as e: """如果输入\,re.compile会报错""" #打印堆栈信息 #traceback.print_exc() # 或者得到堆栈字符串信息 info = traceback.format_exc() print(info) return first = True for parent, dirnames, filenames in os.walk(self.fileDir): #只搜索一层 if not self.dirDeepSearch and not first: break first = False for filename in filenames: if self.filterUseRe: if filterRe.search(filename) is None: continue else: if self.filterRe not in filename: continue self.fileList.append(os.path.join(parent, filename)) # 路径和文件名连接构成完整路径 self._setAllFileList()