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)