# coding: utf-8 from loguru import logger from PyQt6.QtCore import Qt, QTimer from PyQt6.QtGui import QPixmap from qfluentwidgets import ( ImageLabel, InfoBar, InfoBarPosition, IndeterminateProgressBar, MessageBoxBase, PlainTextEdit, PushButton, ) from app.core import (ImageLoaderThread, TextLoaderThread, UpdateFileContentThread) from app.core.services.text_speech import LocalSpeechController from app.view.components.empty_card import EmptyCard # 图片预览类 def createThumbnail(pixmap, max_size=200): """创建快速缩略图""" if pixmap.isNull(): return pixmap # 使用快速缩放算法 return pixmap.scaled( max_size, max_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation, ) class OptimizedPreviewBox(MessageBoxBase): def __init__(self, parent=None, url=None): super().__init__(parent=parent) self.widget.setMinimumSize(500, 500) self.original_pixmap = None self.current_scale = 1.0 # 加载状态显示 self.loadingCard = EmptyCard(self) self.loadingCard.load() self.viewLayout.addWidget(self.loadingCard, 0, Qt.AlignmentFlag.AlignCenter) # 图片显示标签 self.previewLabel = ImageLabel(self) self.previewLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) self.previewLabel.setScaledContents(False) # 重要:禁用自动缩放 self.viewLayout.addWidget(self.previewLabel, 0, Qt.AlignmentFlag.AlignCenter) # 使用优化的图片加载线程 self.imageLoaderThread = ImageLoaderThread(url) self.imageLoaderThread.imageLoaded.connect(self.setPreviewImg) self.imageLoaderThread.errorOccurred.connect(self.handleError) self.imageLoaderThread.progressUpdated.connect(self.updateProgress) # 延迟启动加载,避免阻塞UI初始化 from PyQt6.QtCore import QTimer QTimer.singleShot(100, self.startLoading) def startLoading(self): """开始加载图片""" self.imageLoaderThread.start() def updateProgress(self, progress): """更新加载进度""" self.loadingCard.setText(f"正在加载图片... {progress}%") def setPreviewImg(self, img: QPixmap): """设置预览图片""" self.loadingCard.hide() self.original_pixmap = img # 立即显示缩略图 thumbnail = createThumbnail(img) self.previewLabel.setPixmap(thumbnail) # 然后异步加载高质量版本 self.adjustImageSize() def resizeEvent(self, event): """重写窗口大小改变事件""" super().resizeEvent(event) if self.original_pixmap and not self.original_pixmap.isNull(): # 使用定时器延迟调整,避免频繁调整 from PyQt6.QtCore import QTimer QTimer.singleShot(50, self.adjustImageSize) def adjustImageSize(self): """根据窗口大小动态调整图片尺寸""" if not self.original_pixmap or self.original_pixmap.isNull(): return # 获取可用显示区域大小 margin = 80 available_width = self.width() - margin * 2 available_height = self.height() - margin * 2 # 获取原始图片尺寸 original_width = self.original_pixmap.width() original_height = self.original_pixmap.height() # 计算缩放比例 width_ratio = available_width / original_width height_ratio = available_height / original_height scale_ratio = min(width_ratio, height_ratio, 1.0) # 只在需要时重新缩放 if abs(scale_ratio - self.current_scale) > 0.05: # 变化超过5%才重新缩放 self.current_scale = scale_ratio new_width = int(original_width * scale_ratio) new_height = int(original_height * scale_ratio) # 使用平滑缩放 scaled_pixmap = self.original_pixmap.scaled( new_width, new_height, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) self.previewLabel.setPixmap(scaled_pixmap) def handleError(self, msg): """处理加载错误""" self.loadingCard.error() self.previewLabel.hide() # 文本文档预览类 class PreviewTextBox(MessageBoxBase): """文本预览对话框""" def __init__(self, parent=None, url=None, _id=None): super().__init__(parent=parent) self.updateTxtThread = None self.widget.setMinimumSize(600, 400) self._id = _id self.isChanged = False self.speech_controller = LocalSpeechController(self) self.textSpeakButton = PushButton("朗读文本", self) self.textSpeakButton.hide() self.isSpeaking = False self.textSpeakButton.clicked.connect(self.playTextSpeech) self.viewLayout.addWidget( self.textSpeakButton, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft, ) # 创建文本编辑框 self.textEdit = PlainTextEdit(self) self.textEdit.hide() self.textEdit.setLineWrapMode(PlainTextEdit.LineWrapMode.NoWrap) # 不自动换行 # 设置等宽字体,便于阅读代码或日志 from PyQt6.QtGui import QFont font = QFont("微软雅黑", 10) # 等宽字体 self.textEdit.setFont(font) self.viewLayout.addWidget(self.textEdit) # 加载状态显示 self.loadingCard = EmptyCard(self) self.loadingCard.load() self.viewLayout.addWidget(self.loadingCard, 0, Qt.AlignmentFlag.AlignCenter) # 使用文本加载线程 self.textLoaderThread = TextLoaderThread(url) self.textLoaderThread.textLoaded.connect(self.setTextContent) self.textLoaderThread.errorOccurred.connect(self.handleError) self.textLoaderThread.progressUpdated.connect(self.updateProgress) self.yesButton.hide() # 创建保存按钮 self.saveButton = PushButton("保存修改", self) # 创建进度条 self.saveProgressBar = IndeterminateProgressBar(self) self.saveProgressBar.setFixedHeight(4) self.saveProgressBar.hide() # 添加按钮和进度条到布局 self.buttonLayout.insertWidget( 0, self.saveButton, 1, Qt.AlignmentFlag.AlignVCenter ) self.buttonLayout.insertWidget( 1, self.saveProgressBar, 1, Qt.AlignmentFlag.AlignVCenter ) self.saveButton.setEnabled(False) self.saveButton.clicked.connect(self.saveText) self.cancelButton.setText("返回") # 延迟启动加载,避免阻塞UI初始化 QTimer.singleShot(100, self.startLoading) def saveText(self): logger.info("保存用户修改") # 显示进度条并禁用按钮 self.saveProgressBar.show() self.saveButton.setEnabled(False) self.saveTextThread = UpdateFileContentThread( self._id, self.textEdit.toPlainText(), ) self.saveTextThread.successUpdated.connect(self._successSave) self.saveTextThread.errorUpdated.connect(self._errorSave) self.saveTextThread.start() def _successSave(self): InfoBar.success( "成功", "修改保存成功", Qt.Orientation.Horizontal, True, 1000, InfoBarPosition.TOP_RIGHT, self.window(), ) # 隐藏进度条 self.saveProgressBar.hide() QTimer.singleShot(700, self.accept) def _errorSave(self, msg): InfoBar.error( "失败", msg, Qt.Orientation.Horizontal, True, 1000, InfoBarPosition.TOP_RIGHT, self.window(), ) # 隐藏进度条并重新启用按钮 self.saveProgressBar.hide() self.saveButton.setEnabled(True) def playTextSpeech(self): """播放文本语音""" if not self.isSpeaking: text = self.textEdit.toPlainText() if text and len(text.strip()) > 0: self.speech_controller.play_text(text) self.isSpeaking = True self.textSpeakButton.setText("暂停朗读") else: self.speech_controller.stop_playback() self.isSpeaking = False self.textSpeakButton.setText("朗读文本") def startLoading(self): """开始加载文本""" self.textLoaderThread.start() def updateProgress(self, progress): """更新加载进度""" self.loadingCard.setText(f"正在加载文本... {progress}%") def setTextContent(self, content): """设置文本内容""" self.loadingCard.hide() self.textEdit.show() self.textSpeakButton.show() self.saveButton.setEnabled(True) # 限制显示的内容长度,避免性能问题 max_display_length = 100000 # 最多显示10万个字符 if len(content) > max_display_length: content = ( content[:max_display_length] + f"\n\n... (内容过长,已截断前{max_display_length}个字符,完整内容请下载文件查看)" ) self.textEdit.setPlainText(content) def handleError(self, error_msg): """处理加载错误""" self.loadingCard.error() def resizeEvent(self, event): """重写窗口大小改变事件""" super().resizeEvent(event) # 文本预览框会自动适应大小,无需特殊处理