268 lines
8.4 KiB
Python
268 lines
8.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
GUI 集成示例
|
||
|
|
|
||
|
|
演示如何在 GUI 中集成处理流程和结果展示
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import tkinter as tk
|
||
|
|
from tkinter import ttk, filedialog
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Optional
|
||
|
|
|
||
|
|
# 添加项目根目录到路径
|
||
|
|
project_root = Path(__file__).parent.parent
|
||
|
|
sys.path.insert(0, str(project_root))
|
||
|
|
|
||
|
|
from src.core.processor import ImageProcessor, ProcessCallback, ProcessResult, create_markdown_result
|
||
|
|
from src.config.settings import get_settings
|
||
|
|
from src.utils.logger import init_logger, get_logger
|
||
|
|
from src.gui.widgets import ResultWidget, MessageHandler, ProgressDialog
|
||
|
|
|
||
|
|
|
||
|
|
# 初始化日志
|
||
|
|
log_dir = project_root / "logs"
|
||
|
|
init_logger(log_dir=log_dir, level="INFO", colored_console=True)
|
||
|
|
logger = get_logger(__name__)
|
||
|
|
|
||
|
|
|
||
|
|
class GUIProcessCallback(ProcessCallback):
|
||
|
|
"""
|
||
|
|
GUI 回调类
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, progress_dialog: ProgressDialog, status_label: ttk.Label):
|
||
|
|
super().__init__()
|
||
|
|
self.progress_dialog = progress_dialog
|
||
|
|
self.status_label = status_label
|
||
|
|
|
||
|
|
def on_start(self, message: str = "开始处理"):
|
||
|
|
self.status_label.config(text=message)
|
||
|
|
|
||
|
|
def on_ocr_start(self, message: str = "开始 OCR 识别"):
|
||
|
|
self.status_label.config(text=message)
|
||
|
|
if self.progress_dialog and not self.progress_dialog.is_cancelled():
|
||
|
|
self.progress_dialog.set_message(message)
|
||
|
|
|
||
|
|
def on_ocr_complete(self, result):
|
||
|
|
self.status_label.config(text=f"OCR 完成: {len(result.results)} 行")
|
||
|
|
|
||
|
|
def on_ai_start(self, message: str = "开始 AI 分类"):
|
||
|
|
self.status_label.config(text=message)
|
||
|
|
if self.progress_dialog and not self.progress_dialog.is_cancelled():
|
||
|
|
self.progress_dialog.set_message(message)
|
||
|
|
|
||
|
|
def on_ai_complete(self, result):
|
||
|
|
self.status_label.config(text=f"AI 完成: {result.category.value}")
|
||
|
|
|
||
|
|
def on_save_start(self, message: str = "开始保存"):
|
||
|
|
self.status_label.config(text=message)
|
||
|
|
|
||
|
|
def on_save_complete(self, record_id: int):
|
||
|
|
self.status_label.config(text=f"保存成功: ID={record_id}")
|
||
|
|
|
||
|
|
def on_error(self, message: str, exception=None):
|
||
|
|
self.status_label.config(text=f"错误: {message}")
|
||
|
|
|
||
|
|
def on_complete(self, result: ProcessResult):
|
||
|
|
if result.success:
|
||
|
|
self.status_label.config(text=f"完成! 耗时 {result.process_time:.2f}s")
|
||
|
|
else:
|
||
|
|
self.status_label.config(text="处理失败")
|
||
|
|
|
||
|
|
|
||
|
|
class ProcessorDemoGUI:
|
||
|
|
"""
|
||
|
|
处理器演示 GUI
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.root = tk.Tk()
|
||
|
|
self.root.title("CutThenThink - 处理流程整合演示")
|
||
|
|
self.root.geometry("900x700")
|
||
|
|
|
||
|
|
# 加载配置
|
||
|
|
self.settings = get_settings()
|
||
|
|
|
||
|
|
# 消息处理器
|
||
|
|
self.message_handler = MessageHandler(self.root)
|
||
|
|
|
||
|
|
# 处理器
|
||
|
|
self.processor: Optional[ImageProcessor] = None
|
||
|
|
|
||
|
|
# 创建 UI
|
||
|
|
self._create_ui()
|
||
|
|
|
||
|
|
def _create_ui(self):
|
||
|
|
"""创建 UI"""
|
||
|
|
# 顶部工具栏
|
||
|
|
toolbar = ttk.Frame(self.root, padding=10)
|
||
|
|
toolbar.pack(side=tk.TOP, fill=tk.X)
|
||
|
|
|
||
|
|
# 选择图片按钮
|
||
|
|
self.select_button = ttk.Button(
|
||
|
|
toolbar,
|
||
|
|
text="📁 选择图片",
|
||
|
|
command=self._on_select_image
|
||
|
|
)
|
||
|
|
self.select_button.pack(side=tk.LEFT, padx=5)
|
||
|
|
|
||
|
|
# 处理按钮
|
||
|
|
self.process_button = ttk.Button(
|
||
|
|
toolbar,
|
||
|
|
text="🚀 开始处理",
|
||
|
|
command=self._on_process,
|
||
|
|
state=tk.DISABLED
|
||
|
|
)
|
||
|
|
self.process_button.pack(side=tk.LEFT, padx=5)
|
||
|
|
|
||
|
|
# 分隔符
|
||
|
|
ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=10)
|
||
|
|
|
||
|
|
# 当前图片标签
|
||
|
|
ttk.Label(toolbar, text="当前图片:").pack(side=tk.LEFT, padx=5)
|
||
|
|
self.image_label = ttk.Label(toolbar, text="未选择", foreground="gray")
|
||
|
|
self.image_label.pack(side=tk.LEFT, padx=5)
|
||
|
|
|
||
|
|
# 状态栏
|
||
|
|
self.status_label = ttk.Label(
|
||
|
|
self.root,
|
||
|
|
text="就绪",
|
||
|
|
relief=tk.SUNKEN,
|
||
|
|
anchor=tk.W,
|
||
|
|
padding=(5, 2)
|
||
|
|
)
|
||
|
|
self.status_label.pack(side=tk.BOTTOM, fill=tk.X)
|
||
|
|
|
||
|
|
# 主内容区域(使用 PanedWindow 分割)
|
||
|
|
paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
|
||
|
|
paned.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||
|
|
|
||
|
|
# 左侧:结果展示
|
||
|
|
left_frame = ttk.LabelFrame(paned, text="处理结果", padding=5)
|
||
|
|
paned.add(left_frame, weight=2)
|
||
|
|
|
||
|
|
self.result_widget = ResultWidget(left_frame)
|
||
|
|
self.result_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||
|
|
|
||
|
|
# 右侧:日志
|
||
|
|
right_frame = ttk.LabelFrame(paned, text="处理日志", padding=5)
|
||
|
|
paned.add(right_frame, weight=1)
|
||
|
|
|
||
|
|
self.log_text = tk.Text(
|
||
|
|
right_frame,
|
||
|
|
wrap=tk.WORD,
|
||
|
|
font=("Consolas", 9),
|
||
|
|
state=tk.DISABLED
|
||
|
|
)
|
||
|
|
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||
|
|
|
||
|
|
# 配置日志标签
|
||
|
|
self.log_text.tag_config("INFO", foreground="#3498db")
|
||
|
|
self.log_text.tag_config("WARNING", foreground="#f39c12")
|
||
|
|
self.log_text.tag_config("ERROR", foreground="#e74c3c")
|
||
|
|
self.log_text.tag_config("SUCCESS", foreground="#27ae60")
|
||
|
|
|
||
|
|
def _log(self, level: str, message: str):
|
||
|
|
"""添加日志"""
|
||
|
|
self.log_text.config(state=tk.NORMAL)
|
||
|
|
self.log_text.insert(tk.END, f"{message}\n", level)
|
||
|
|
self.log_text.see(tk.END)
|
||
|
|
self.log_text.config(state=tk.DISABLED)
|
||
|
|
self.root.update()
|
||
|
|
|
||
|
|
def _on_select_image(self):
|
||
|
|
"""选择图片"""
|
||
|
|
filename = filedialog.askopenfilename(
|
||
|
|
title="选择图片",
|
||
|
|
filetypes=[
|
||
|
|
("图片文件", "*.png *.jpg *.jpeg *.bmp *.gif"),
|
||
|
|
("所有文件", "*.*")
|
||
|
|
]
|
||
|
|
)
|
||
|
|
|
||
|
|
if filename:
|
||
|
|
self.current_image_path = filename
|
||
|
|
self.image_label.config(text=Path(filename).name)
|
||
|
|
self.process_button.config(state=tk.NORMAL)
|
||
|
|
self._log("INFO", f"已选择图片: {filename}")
|
||
|
|
|
||
|
|
def _on_process(self):
|
||
|
|
"""处理图片"""
|
||
|
|
if not hasattr(self, 'current_image_path'):
|
||
|
|
return
|
||
|
|
|
||
|
|
# 创建进度对话框
|
||
|
|
progress_dialog = ProgressDialog(
|
||
|
|
self.root,
|
||
|
|
title="处理中",
|
||
|
|
message="正在处理图片...",
|
||
|
|
cancelable=True
|
||
|
|
)
|
||
|
|
|
||
|
|
# 创建回调
|
||
|
|
callback = GUIProcessCallback(progress_dialog, self.status_label)
|
||
|
|
|
||
|
|
# 创建处理器
|
||
|
|
self.processor = ImageProcessor(
|
||
|
|
ocr_config={
|
||
|
|
'mode': self.settings.ocr.mode.value,
|
||
|
|
'lang': self.settings.ocr.lang,
|
||
|
|
'use_gpu': self.settings.ocr.use_gpu
|
||
|
|
},
|
||
|
|
ai_config=self.settings.ai,
|
||
|
|
db_path=str(project_root / "data" / "cutnthink.db"),
|
||
|
|
callback=callback
|
||
|
|
)
|
||
|
|
|
||
|
|
# 处理图片(在后台执行)
|
||
|
|
self.root.after(100, lambda: self._do_process(progress_dialog))
|
||
|
|
|
||
|
|
def _do_process(self, progress_dialog: ProgressDialog):
|
||
|
|
"""执行处理"""
|
||
|
|
try:
|
||
|
|
self._log("INFO", "开始处理...")
|
||
|
|
|
||
|
|
# 处理
|
||
|
|
result = self.processor.process_image(self.current_image_path)
|
||
|
|
|
||
|
|
# 关闭进度对话框
|
||
|
|
if not progress_dialog.is_cancelled():
|
||
|
|
progress_dialog.close()
|
||
|
|
|
||
|
|
# 显示结果
|
||
|
|
if result.success:
|
||
|
|
self.result_widget.set_result(result)
|
||
|
|
self._log("SUCCESS", f"处理成功! 耗时 {result.process_time:.2f}s")
|
||
|
|
self.message_handler.show_info(
|
||
|
|
"处理完成",
|
||
|
|
f"处理成功!\n耗时: {result.process_time:.2f}秒\n记录ID: {result.record_id}"
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
self._log("ERROR", f"处理失败: {result.error_message}")
|
||
|
|
self.message_handler.show_error(
|
||
|
|
"处理失败",
|
||
|
|
result.error_message or "未知错误"
|
||
|
|
)
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
progress_dialog.close()
|
||
|
|
logger.error(f"处理失败: {e}", exc_info=True)
|
||
|
|
self._log("ERROR", f"处理失败: {e}")
|
||
|
|
self.message_handler.show_error("处理失败", str(e), exception=e)
|
||
|
|
|
||
|
|
def run(self):
|
||
|
|
"""运行 GUI"""
|
||
|
|
self.root.mainloop()
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""主函数"""
|
||
|
|
app = ProcessorDemoGUI()
|
||
|
|
app.run()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|