From e1c2e75a344fc129a4760fc68529529f93758f6e Mon Sep 17 00:00:00 2001 From: Rose <12210429@mail.sustech.edu.cn> Date: Wed, 14 May 2025 02:11:45 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=BC=B9=E7=AA=97=EF=BC=9A?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=20OCR=20=E6=96=87=E6=9C=AC=20+=20=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=20/=20=E7=BF=BB=E8=AF=91=20/=20=E6=9C=97=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- textshot/history_window.py | 48 ++++++++++++++++++++++++++++ textshot/result_dialog.py | 64 ++++++++++++++++++++++++++++++++++++++ textshot/textshot.py | 16 ++++++++++ 3 files changed, 128 insertions(+) create mode 100644 textshot/history_window.py create mode 100644 textshot/result_dialog.py diff --git a/textshot/history_window.py b/textshot/history_window.py new file mode 100644 index 0000000..f9a55f4 --- /dev/null +++ b/textshot/history_window.py @@ -0,0 +1,48 @@ +""" +历史记录侧边窗:保存最近 N 条 OCR 文本 +""" +from collections import deque +from PyQt5.QtWidgets import QMainWindow, QListWidget, QListWidgetItem +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFont +import pyperclip +from .result_dialog import ResultDialog + +MAX_HISTORY = 20 + + +class HistoryWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("OCR 历史") + self.resize(300, 400) + self.list_widget = QListWidget() + self.setCentralWidget(self.list_widget) + self.history = deque(maxlen=MAX_HISTORY) # 左边索引 0 是最新 + + # 双击查看 + self.list_widget.itemDoubleClicked.connect(self.open_record) + + # 美化 + font = QFont() + font.setPointSize(10) + self.list_widget.setFont(font) + + # ------ 对外接口 ------ + def add_record(self, text: str): + self.history.appendleft(text) + self.refresh_ui() + + # ------ 私有 ------ + def refresh_ui(self): + self.list_widget.clear() + for idx, t in enumerate(self.history): + preview = (t[:40] + "…") if len(t) > 40 else t + self.list_widget.addItem(f"{idx + 1}. {preview}") + + def open_record(self, item: QListWidgetItem): + idx = int(item.text().split(".")[0]) - 1 + full_text = self.history[idx] + pyperclip.copy(full_text) + dlg = ResultDialog(full_text, self.add_record, self) + dlg.exec() diff --git a/textshot/result_dialog.py b/textshot/result_dialog.py new file mode 100644 index 0000000..5277172 --- /dev/null +++ b/textshot/result_dialog.py @@ -0,0 +1,64 @@ +""" +结果弹窗:显示 OCR 文本 + 复制 / 翻译 / 朗读 +""" +from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QTextEdit, + QPushButton, QHBoxLayout, QMessageBox, QApplication) +from PyQt5.QtCore import Qt +import pyperclip +from googletrans import Translator +import pyttsx3 + + +class ResultDialog(QDialog): + def __init__(self, text: str, history_cb, parent=None): + super().__init__(parent) + self.setWindowTitle("TextShot – OCR 结果") + self.resize(500, 340) + self.setWindowModality(Qt.WindowModality.ApplicationModal) + + self.text = text + self.history_cb = history_cb # 回调:写入历史 + + # ========= UI ========= + edit = QTextEdit(readOnly=True) + edit.setPlainText(text) + + btn_copy = QPushButton("复制") + btn_copy.clicked.connect(self.copy_text) + + btn_translate = QPushButton("翻译") + btn_translate.clicked.connect(self.translate_text) + + btn_speak = QPushButton("朗读") + btn_speak.clicked.connect(self.speak_text) + + h = QHBoxLayout() + for b in (btn_copy, btn_translate, btn_speak): + h.addWidget(b) + h.addStretch() + + v = QVBoxLayout(self) + v.addWidget(edit) + v.addLayout(h) + + # 首次打开即写入历史 + self.history_cb(text) + + # ---------- 功能 ---------- + def copy_text(self): + pyperclip.copy(self.text) + + def translate_text(self): + try: + res = Translator().translate(self.text, dest="zh-cn").text + QMessageBox.information(self, "翻译结果", res) + except Exception as e: + QMessageBox.warning(self, "翻译失败", str(e)) + + def speak_text(self): + try: + engine = pyttsx3.init() + engine.say(self.text) + engine.runAndWait() + except Exception as e: + QMessageBox.warning(self, "朗读失败", str(e)) diff --git a/textshot/textshot.py b/textshot/textshot.py index 7e350b0..bffc859 100755 --- a/textshot/textshot.py +++ b/textshot/textshot.py @@ -5,6 +5,7 @@ import sys import pyperclip +from .history_window import HistoryWindow from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt, QTimer @@ -12,6 +13,7 @@ from .notifications import notify_copied, notify_ocr_failure from .ocr import ensure_tesseract_installed, get_ocr_result +_history_win = None # 单例历史窗口 class Snipper(QtWidgets.QWidget): def __init__(self, parent, langs=None, flags=Qt.WindowFlags()): @@ -100,7 +102,21 @@ def mouseReleaseEvent(self, event): return super().mouseReleaseEvent(event) ocr_result = self.snipOcr() + # if ocr_result: + # pyperclip.copy(ocr_result) + # log_copied(ocr_result) + # notify_copied(ocr_result) if ocr_result: + # ---------- 弹窗 ---------- + from .result_dialog import ResultDialog + global _history_win # 单例 + if _history_win is None: + _history_win = HistoryWindow() + _history_win.show() # 模态外常驻 + dlg = ResultDialog(ocr_result, _history_win.add_record) + dlg.exec() + + # ---------- 原有逻辑 ---------- pyperclip.copy(ocr_result) log_copied(ocr_result) notify_copied(ocr_result)