Files
cutThenThink/src/gui/main_window.py
congsh 313e1f40d8 refactor: 重构为纯云端版本,移除所有本地ML库依赖
重大更改:
1. requirements.txt - 移除 paddleocr/paddlepaddle,使用纯 API 版本
2. src/core/ocr.py - 完全重写
   - 移除 PaddleOCREngine 和 ensure_paddleocr()
   - 移除 numpy 依赖(不再需要)
   - 实现完整的 CloudOCREngine
   - 支持百度/腾讯/阿里云 OCR API
   - 添加自定义 API 支持
3. src/config/settings.py - 简化 OCR 配置
   - OCRMode 枚举仅保留 CLOUD
   - OCRConfig 添加 provider 字段
4. src/core/__init__.py - 移除 PaddleOCREngine 导出
5. src/gui/main_window.py - 移除 ensure_paddleocr 导入
6. build.bat/build.sh - 简化构建参数
   - 移除所有 ML 库的 --exclude-module
   - 移除 pyi_hooks 依赖
   - 添加 openai/anthropic hidden-import

测试:
- ✓ 所有核心模块导入成功
- ✓ 没有 PaddleOCR 相关错误

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 13:42:46 +08:00

600 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
主窗口模块
实现应用程序的主窗口,包括侧边栏导航和主内容区域
集成图片处理功能
"""
from PyQt6.QtWidgets import (
QMainWindow,
QWidget,
QHBoxLayout,
QVBoxLayout,
QPushButton,
QStackedWidget,
QLabel,
QFrame,
QScrollArea,
QApplication,
QFileDialog,
QMessageBox
)
from PyQt6.QtCore import Qt, QSize, pyqtSignal, QThread, QTimer
from PyQt6.QtGui import QIcon, QShortcut, QKeySequence
from src.gui.styles import ThemeStyles
from src.gui.widgets import (
ScreenshotWidget,
ClipboardMonitor,
ImagePicker,
ImagePreviewWidget,
QuickScreenshotHelper,
ClipboardImagePicker
)
from src.gui.widgets.message_handler import show_info, show_error
class MainWindow(QMainWindow):
"""主窗口类"""
# 信号:图片加载完成
image_loaded = pyqtSignal(str)
def __init__(self):
"""初始化主窗口"""
super().__init__()
self.setWindowTitle("CutThenThink - 智能截图管理")
self.setMinimumSize(1000, 700)
self.resize(1200, 800)
# 图片处理组件
self.screenshot_widget = None
self.clipboard_monitor = None
self.current_image_path = None
# 初始化 UI
self._init_ui()
self._apply_styles()
self._init_shortcuts()
# 初始化图片处理组件
self._init_image_components()
def _init_ui(self):
"""初始化用户界面"""
# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# 创建侧边栏
self._create_sidebar(main_layout)
# 创建主内容区域
self._create_content_area(main_layout)
def _create_sidebar(self, parent_layout):
"""
创建侧边栏
Args:
parent_layout: 父布局
"""
# 侧边栏容器
sidebar = QWidget()
sidebar.setObjectName("sidebar")
sidebar.setFixedWidth(240)
# 侧边栏布局
sidebar_layout = QVBoxLayout(sidebar)
sidebar_layout.setContentsMargins(8, 16, 8, 16)
sidebar_layout.setSpacing(4)
# 应用标题
app_title = QLabel("CutThenThink")
app_title.setStyleSheet("""
QLabel {
color: #8B6914;
font-size: 20px;
font-weight: 700;
padding: 8px;
}
""")
sidebar_layout.addWidget(app_title)
# 添加分隔线
separator1 = self._create_separator()
sidebar_layout.addWidget(separator1)
# 导航按钮组
self.nav_buttons = {}
nav_items = [
("screenshot", "📷 截图处理", "screenshot"),
("browse", "📁 分类浏览", "browse"),
("upload", "☁️ 批量上传", "upload"),
("settings", "⚙️ 设置", "settings"),
]
for nav_id, text, _icon_name in nav_items:
button = NavigationButton(text)
button.clicked.connect(lambda checked, nid=nav_id: self._on_nav_clicked(nid))
sidebar_layout.addWidget(button)
self.nav_buttons[nav_id] = button
# 添加弹性空间
sidebar_layout.addStretch()
# 底部信息
version_label = QLabel("v0.1.0")
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
version_label.setStyleSheet("""
QLabel {
color: #999999;
font-size: 11px;
padding: 8px;
}
""")
sidebar_layout.addWidget(version_label)
# 添加到主布局
parent_layout.addWidget(sidebar)
# 设置默认选中的导航项
self.nav_buttons["screenshot"].setChecked(True)
self.current_nav = "screenshot"
def _create_content_area(self, parent_layout):
"""
创建主内容区域
Args:
parent_layout: 父布局
"""
# 内容区域容器
content_area = QWidget()
content_area.setObjectName("contentArea")
# 内容区域布局
content_layout = QVBoxLayout(content_area)
content_layout.setContentsMargins(24, 16, 24, 16)
content_layout.setSpacing(16)
# 创建堆栈部件用于切换页面
self.content_stack = QStackedWidget()
self.content_stack.setObjectName("contentStack")
# 创建各个页面
self._create_pages()
content_layout.addWidget(self.content_stack)
# 添加到主布局
parent_layout.addWidget(content_area)
def _create_pages(self):
"""创建各个页面"""
# 截图处理页面
screenshot_page = self._create_screenshot_page()
self.content_stack.addWidget(screenshot_page)
# 分类浏览页面
browse_page = self._create_browse_page()
self.content_stack.addWidget(browse_page)
# 批量上传页面
upload_page = self._create_upload_page()
self.content_stack.addWidget(upload_page)
# 设置页面
settings_page = self._create_settings_page()
self.content_stack.addWidget(settings_page)
def _create_screenshot_page(self) -> QWidget:
"""创建截图处理页面"""
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(16)
# 页面标题
title = QLabel("📷 截图处理")
title.setObjectName("pageTitle")
layout.addWidget(title)
# 快捷操作按钮区域
actions_card = self._create_card("")
actions_layout = QVBoxLayout(actions_card)
actions_layout.setSpacing(12)
actions_title = QLabel("快捷操作")
actions_title.setObjectName("sectionTitle")
actions_layout.addWidget(actions_title)
# 新建截图按钮
self.new_screenshot_btn = QPushButton("📷 新建截图")
self.new_screenshot_btn.setObjectName("primaryButton")
self.new_screenshot_btn.setMinimumHeight(44)
self.new_screenshot_btn.setToolTip("快捷键: Ctrl+Shift+A")
self.new_screenshot_btn.clicked.connect(self._on_new_screenshot)
actions_layout.addWidget(self.new_screenshot_btn)
# 导入图片按钮
self.import_image_btn = QPushButton("📂 导入图片")
self.import_image_btn.setMinimumHeight(44)
self.import_image_btn.clicked.connect(self._on_import_image)
actions_layout.addWidget(self.import_image_btn)
# 粘贴剪贴板图片按钮
self.paste_btn = QPushButton("📋 粘贴剪贴板图片")
self.paste_btn.setMinimumHeight(44)
self.paste_btn.setToolTip("快捷键: Ctrl+Shift+V")
self.paste_btn.clicked.connect(self._on_paste_clipboard)
actions_layout.addWidget(self.paste_btn)
layout.addWidget(actions_card)
# 图片预览区域
preview_card = self._create_card("")
preview_layout = QVBoxLayout(preview_card)
preview_layout.setContentsMargins(16, 16, 16, 16)
preview_layout.setSpacing(12)
preview_title = QLabel("图片预览")
preview_title.setObjectName("sectionTitle")
preview_layout.addWidget(preview_title)
# 创建图片预览组件
self.image_preview = ImagePreviewWidget()
preview_layout.addWidget(self.image_preview)
layout.addWidget(preview_card, 1)
return page
def _create_browse_page(self) -> QWidget:
"""创建分类浏览页面"""
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(16)
# 页面标题
title = QLabel("📁 分类浏览")
title.setObjectName("pageTitle")
layout.addWidget(title)
# 内容卡片
content_card = self._create_card("""
<h3>浏览截图</h3>
<p>这里将显示您的所有截图和分类。</p>
<p>支持的浏览方式:</p>
<ul>
<li>🏷️ 按标签浏览</li>
<li>📅 按日期浏览</li>
<li>🔍 搜索和筛选</li>
</ul>
""")
layout.addWidget(content_card)
# 添加弹性空间
layout.addStretch()
return page
def _create_upload_page(self) -> QWidget:
"""创建批量上传页面"""
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(16)
# 页面标题
title = QLabel("☁️ 批量上传")
title.setObjectName("pageTitle")
layout.addWidget(title)
# 内容卡片
content_card = self._create_card("""
<h3>云存储上传</h3>
<p>这里将管理您的云存储上传任务。</p>
<p>功能包括:</p>
<ul>
<li>📤 批量上传截图</li>
<li>🔄 同步状态监控</li>
<li>📊 上传历史记录</li>
</ul>
""")
layout.addWidget(content_card)
# 添加弹性空间
layout.addStretch()
return page
def _create_settings_page(self) -> QWidget:
"""创建设置页面"""
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(16)
# 页面标题
title = QLabel("⚙️ 设置")
title.setObjectName("pageTitle")
layout.addWidget(title)
# 使用滚动区域以支持大量设置项
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll_content = QWidget()
scroll_layout = QVBoxLayout(scroll_content)
scroll_layout.setSpacing(16)
# AI 配置卡片
ai_card = self._create_card("""
<h3>🤖 AI 配置</h3>
<p>配置您的 AI 服务提供商和 API 设置。</p>
<p>支持OpenAI、Anthropic、Azure</p>
""")
scroll_layout.addWidget(ai_card)
# OCR 配置卡片
ocr_card = self._create_card("""
<h3>🔍 OCR 配置</h3>
<p>配置云端 OCR 服务。</p>
<p>支持:百度 OCR、腾讯云 OCR、阿里云 OCR、自定义 API</p>
""")
scroll_layout.addWidget(ocr_card)
# 云存储配置卡片
cloud_card = self._create_card("""
<h3>☁️ 云存储配置</h3>
<p>配置云存储服务用于同步。</p>
<p>支持S3、OSS、COS、MinIO</p>
""")
scroll_layout.addWidget(cloud_card)
# 界面配置卡片
ui_card = self._create_card("""
<h3>🎨 界面配置</h3>
<p>自定义应用程序外观和行为。</p>
<p>主题、语言、快捷键等</p>
""")
scroll_layout.addWidget(ui_card)
# 添加弹性空间
scroll_layout.addStretch()
scroll.setWidget(scroll_content)
layout.addWidget(scroll)
return page
def _create_card(self, content_html: str) -> QWidget:
"""
创建卡片部件
Args:
content_html: 卡片内容的 HTML
Returns:
卡片部件
"""
card = QWidget()
card.setObjectName("card")
layout = QVBoxLayout(card)
layout.setContentsMargins(0, 0, 0, 0)
label = QLabel(content_html)
label.setWordWrap(True)
label.setTextFormat(Qt.TextFormat.RichText)
label.setStyleSheet("""
QLabel {
color: #2C2C2C;
font-size: 14px;
line-height: 1.6;
}
QLabel h2 {
color: #8B6914;
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
QLabel h3 {
color: #8B6914;
font-size: 16px;
font-weight: 600;
margin-bottom: 6px;
}
QLabel p {
margin: 4px 0;
}
QLabel ul {
margin: 8px 0;
padding-left: 20px;
}
QLabel li {
margin: 4px 0;
}
""")
layout.addWidget(label)
return card
def _create_separator(self) -> QFrame:
"""创建分隔线"""
separator = QFrame()
separator.setObjectName("navSeparator")
return separator
def _on_nav_clicked(self, nav_id: str):
"""
导航按钮点击处理
Args:
nav_id: 导航项 ID
"""
# 取消当前选中的按钮
if self.current_nav in self.nav_buttons:
self.nav_buttons[self.current_nav].setChecked(False)
# 选中新按钮
self.nav_buttons[nav_id].setChecked(True)
self.current_nav = nav_id
# 切换页面
nav_order = ["screenshot", "browse", "upload", "settings"]
if nav_id in nav_order:
index = nav_order.index(nav_id)
self.content_stack.setCurrentIndex(index)
def _apply_styles(self):
"""应用样式表"""
ThemeStyles.apply_style(self)
def _init_shortcuts(self):
"""初始化全局快捷键"""
# 截图快捷键 Ctrl+Shift+A
screenshot_shortcut = QShortcut(QKeySequence("Ctrl+Shift+A"), self)
screenshot_shortcut.activated.connect(self._on_new_screenshot)
# 粘贴剪贴板快捷键 Ctrl+Shift+V
paste_shortcut = QShortcut(QKeySequence("Ctrl+Shift+V"), self)
paste_shortcut.activated.connect(self._on_paste_clipboard)
# 导入图片快捷键 Ctrl+Shift+O
import_shortcut = QShortcut(QKeySequence("Ctrl+Shift+O"), self)
import_shortcut.activated.connect(self._on_import_image)
def _init_image_components(self):
"""初始化图片处理组件"""
# 创建截图组件
self.screenshot_widget = ScreenshotWidget(self)
self.screenshot_widget.screenshot_saved.connect(self._on_screenshot_saved)
# 注册到全局助手
QuickScreenshotHelper.set_screenshot_widget(self.screenshot_widget)
# 创建剪贴板监听器
self.clipboard_monitor = ClipboardMonitor(self)
self.clipboard_monitor.image_detected.connect(self._on_clipboard_image_detected)
# ========== 图片处理方法 ==========
def _on_new_screenshot(self):
"""新建截图"""
if self.screenshot_widget:
self.screenshot_widget.take_screenshot()
def _on_screenshot_saved(self, filepath: str):
"""
截图保存完成回调
Args:
filepath: 保存的文件路径
"""
self.current_image_path = filepath
# 加载到预览组件
self.image_preview.load_image(filepath)
show_info(self, "截图完成", f"截图已保存到:\n{filepath}")
self.image_loaded.emit(filepath)
def _on_import_image(self):
"""导入图片"""
filepath, _ = QFileDialog.getOpenFileName(
self,
"选择图片",
"",
"图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.webp);;所有文件 (*.*)"
)
if filepath:
self.current_image_path = filepath
self.image_preview.load_image(filepath)
show_info(self, "导入成功", f"图片已导入:\n{filepath}")
self.image_loaded.emit(filepath)
def _on_paste_clipboard(self):
"""粘贴剪贴板图片"""
clipboard = QApplication.clipboard()
pixmap = clipboard.pixmap()
if pixmap.isNull():
show_error(self, "错误", "剪贴板中没有图片")
return
# 保存剪贴板图片
from datetime import datetime
from pathlib import Path
import tempfile
temp_dir = Path(tempfile.gettempdir()) / "cutthenthink" / "clipboard"
temp_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filepath = temp_dir / f"clipboard_{timestamp}.png"
if pixmap.save(str(filepath)):
self.current_image_path = filepath
self.image_preview.load_image(filepath)
show_info(self, "粘贴成功", f"剪贴板图片已保存:\n{filepath}")
self.image_loaded.emit(filepath)
else:
show_error(self, "错误", "保存剪贴板图片失败")
def _on_clipboard_image_detected(self, filepath: str):
"""
剪贴板图片检测回调
Args:
filepath: 保存的图片路径
"""
# 可选:自动加载剪贴板图片或显示通知
pass
class NavigationButton(QPushButton):
"""导航按钮类"""
def __init__(self, text: str, icon_path: str = None, parent=None):
"""
初始化导航按钮
Args:
text: 按钮文字
icon_path: 图标路径(可选)
parent: 父部件
"""
super().__init__(text, parent)
self.setObjectName("navButton")
self.setCheckable(True)
self.setMinimumHeight(44)
# 如果有图标,加载图标
if icon_path:
self.setIcon(QIcon(icon_path))
self.setIconSize(QSize(20, 20))
def main():
"""
应用程序入口
初始化并显示主窗口
"""
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())